diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..23b62759
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.py]
+indent_style = space
+indent_size = 4
+insert_final_newline = false
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..2fec1386
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,96 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+.pytest_cache
+
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# dotenv
+.env
+
+# virtualenv
+.venv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+
+# Rope project settings
+.ropeproject
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..2a7b7ecc
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,10 @@
+image: python:3
+
+test:
+ before_script:
+ - apt-get update -qy
+ - apt-get install -y libsndfile1
+ - pip install tox
+ script:
+ - tox
+ coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..951e346f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,11 @@
+Copyright 2018 EBU ADM Renderer Authors
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..556aef65
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include README.md
+include ear/core/data/README.md
+include tox.ini
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..356c342a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,156 @@
+# EBU ADM Renderer (EAR)
+
+The **EBU ADM Renderer** **(*EAR*)** is a complete interpretation of the **Audio Definition Model (ADM)** format, specified in Recommendation [ITU-R BS.2076-1](https://www.itu.int/rec/R-REC-BS.2076/en). ADM is the recommended format for all stages and use cases within the scope of programme productions of **Next Generation Audio (NGA)**. This repository contains a Python reference implementation of the EBU ADM Renderer.
+
+This Renderer implementation is capable of rendering audio signals to all reproduction systems mentioned in ["Advanced sound system for programme production (ITU-R BS.2051-1)"](https://www.itu.int/rec/R-REC-BS.2051/en).
+
+Further descriptions of the *EAR* algorithms and functionalities can be found in EBU Tech 3388.
+
+## Test files
+A initial set of ADM files to test the *EAR* can be found under
+ - https://ebu.io/qc/testmaterial and
+ - http://cvssp.org/data/s3a/public/radiodrama_register.php
+
+## Installation
+
+To install the latest release from PyPi:
+
+```bash
+$ pip install ear
+```
+
+### Python versions
+
+*EAR* supports Python 2.7 and Python >=3.6
+and runs on all major platforms (Linux, Mac OSX, Windows).
+
+### Installation of extra packages
+
+If you want to run the unit tests you can install all extra requirements with pip:
+```bash
+$ pip install ear[test]
+```
+
+## Getting started
+
+The *EAR* reference implementation comes with two command line tools:
+
+- `ear-render` Is the main tool to render BW64/ADM audio files
+- `ear-utils` Collection of useful ADM utilities
+
+### Command line renderer
+
+```bash
+usage: ear-render [-h] [-d] -s target_system [-l layout_file]
+ [--output-gain-db gain_db] [--fail-on-overload]
+ [--enable-block-duration-fix] [--strict]
+ input_file output_file
+
+EBU ADM renderer
+
+positional arguments:
+ input_file
+ output_file
+
+optional arguments:
+ -h, --help show this help message and exit
+ -d, --debug print debug information when an error occurres
+ -s target_system, --system target_system
+ Target output system, accoring to ITU-R BS.2051.
+ Available systems are: 0+2+0, 0+5+0, 2+5+0, 4+5+0,
+ 4+5+1, 3+7+0, 4+9+0, 9+10+3, 0+7+0, 4+7+0
+ -l layout_file, --layout layout_file
+ Layout config file
+ --output-gain-db gain_db
+ output gain in dB (default: 0)
+ --fail-on-overload, -c
+ fail if an overload condition is detected in the
+ output
+ --enable-block-duration-fix
+ automatically try to fix faulty block format durations
+ --strict treat unknown ADM attributes as errors
+```
+
+To render an ADM file, the following three parameters must be given:
+ - `-s` followed by the target output format to render to
+ - the name of the input file
+ - the name of the output file
+
+For example `ear-render -s 0+5+0 input.wav output_surround.wav` will render the BW64/ADM file `input.wav` to a `0+5+0` target speaker layout and store the result in `output_surround.wav`.
+
+The *optional* `--layout` parameter allows to specify the real loudspeaker positions and screen dimensions of a reproduction setup.
+Refer to [the layout file documentation](doc/layout_file.md) for more information about its format.
+
+`--fail-on-overload` makes the rendering process fail in case an overload in the output channels to ensure any signal clipping doesn't go unnoticed. Use `--output-gain-db` to adjust the output gain.
+
+`--enable-block-duration-fix` automatically fixes durations of `audioBlockFormats` in case they are not continuous.
+**Please note** that the proper way to handle this situation is to fix the input file.
+
+`--strict` enables strict ADM parsing mode. Some of the currently available
+ADM/BW64 files may not strictly adhere to the BS.2076 specification, for example by including xml attributes that are not part of the standard.
+The default behaviour is to output a warning and continue processing.
+When strict mode is enabled, warnings are turned into errors and processing is stopped.
+
+
+**Please note** that, depending on the size of the file, it may
+take some time to render the file. At the time of writing, the parsing of the ADM XML data is relatively slow when the ADM is large (>= a few megabytes).
+
+### Command line ADM utilities
+
+The `ear-utils` command provides various subcommands which can be seen on the help message
+when called with `ear-utils --help`:
+
+Each subcommand may have its own command line options, which can be
+displayed using `ear-utils SUBCOMMAND --help`, where `SUBCOMMAND` is one of the supported subcommands.
+
+```bash
+usage: ear-utils [-h]
+ {make_test_bwf,replace_axml,dump_axml,dump_chna,ambix_to_bwf}
+ ...
+
+EBU ADM renderer utilities
+
+optional arguments:
+ -h, --help show this help message and exit
+
+available subcommands:
+ {make_test_bwf,replace_axml,dump_axml,dump_chna,ambix_to_bwf}
+ make_test_bwf make a bwf file from a wav file and some metadata
+ replace_axml replace the axml chunk in an existing ADM BWF file
+ dump_axml dump the axml chunk of an ADM BWF file to stdout
+ dump_chna dump the chna chunk of an ADM BWF file to stdout
+ ambix_to_bwf make a BWF file from an ambix format HOA file
+```
+
+#### HOA ADM Creation
+```bash
+usage: ear-utils ambix_to_bwf [-h] [--norm NORM] [--nfcDist NFCDIST]
+ [--screenRef] [--chna-only]
+ input output
+
+positional arguments:
+ input input file
+ output output BWF file
+
+optional arguments:
+ -h, --help show this help message and exit
+ --norm NORM normalization mode
+ --nfcDist NFCDIST Near-Field Compensation Distance (float)
+ --screenRef Screen Reference
+ --chna-only use only CHNA with common definitions
+```
+
+
+To convert an ambiX file in an ADM one, the following two parameters must be given:
+- the name of the input file
+- the name of the output file
+
+The optional parameters are :
+- The normalization of the signals (N3D, FuMa or SN3D, which is the default value)
+- The NFC Distance, i.e., the distance at which the HOA mix was created. A float value between 0 and 20 meters must be given.
+ The default value 0 means no NFC processing.
+- The screenRef flag, which tells if the audio content is screen related or not. The default value is False, which means no screen scaling.
+
+For example, `ear-utils ambix_to_bwf --nfcDist 2.53 input.wav output.wav` will create an ADM file called output.wav containing the audio samples of the input.wav file and the ADM metadata corresponding to an ambiX file with SN3D normalization, an 2.53 meters nfcDist, and no screen scaling.
+
+**Please note** that the software implicitly assumes that all the HOA channels are in ACN ordering and that no channel is missing. For example, it will assumes the signal is a 4th order HOA signal if it finds 25 channels ((4+1)²=25).
diff --git a/doc/layout_file.md b/doc/layout_file.md
new file mode 100644
index 00000000..af6d8d0a
--- /dev/null
+++ b/doc/layout_file.md
@@ -0,0 +1,119 @@
+Information about the loudspeaker layout can be passed to the renderer by using
+a speakers file, which is passed to the renderer using the `--speakers` flag.
+
+# File Format
+
+A speakers file is a [YAML](https://en.wikipedia.org/wiki/YAML) document, which
+contains a list of loudspeakers under the `speakers` key, and the screen
+information under the `screen` key. Either may be omitted if not required.
+
+## Speakers list
+
+The top level `speakers` item should contain a sequence of mappings, one for
+each output loudspeaker.
+
+Each mapping should look something like this:
+
+```yaml
+- {channel: 7, names: M+000, position: {az: 0.0, el: 0.0, r: 2.0 }}
+```
+
+which defines a loudspeaker connected to channel 7 (zero based), assigned to
+M+000 (in bs.2051 terms), with a given position. The file should contain a
+sequence of lines as above; one line per speaker.
+
+The possible keys are as follows:
+
+### `channel` (required)
+
+The zero-based output channel number.
+
+### `names` (required)
+
+A list (or a single string) of BS.2051 channel names that this speaker should
+handle, i.e. like `M+000` or `[U+180, UH+180]`.
+
+### `position` (optional)
+
+A mapping containing the real loudspeaker position, with keys `az`, `el` and
+`r` specifying the azimuth, elevation and distance of the loudspeaker in ADM
+angle format (anticlockwise azimuth) and metres.
+
+### `gain_linear` (optional)
+
+A linear gain to apply to this output channel; this is useful for LFE outputs.
+
+## Screen
+
+The top level `screen` item should contain a mapping, with at least a `type`
+key, and the following options, depending on the type. If the screen key is
+omitted, the default polar screen position specified in BS.2076-1 will be
+assumed. If a null screen is specified, then screen-related processing will not
+be applied.
+
+### if `type == "polar"`
+
+#### `aspectRatio` (required)
+
+Screen width divided by screen height
+
+#### `centrePosition` (required)
+
+Polar position of the centre of the screen, in the same format as the speaker `position` attribute.
+
+#### `widthAzimuth` (required)
+
+Width of the screen in degrees.
+
+### if `type == "cart"`
+
+#### `aspectRatio` (required)
+
+Screen width divided by screen height
+
+#### `centrePosition` (required)
+
+Cartesian position of the centre of the screen; a mapping with keys `X`, `Y` and `Z`.
+
+#### `widthX` (required)
+
+Width of the screen in Cartesian coordinates.
+
+# Examples
+
+Useful speakers files should be stored in `doc/speakers_files/`.
+
+A minimal example with a polar screen would look like:
+
+```yaml
+speakers:
+ - {channel: 0, names: M+030, position: {az: 30.0, el: 0.0, r: 2.0 }}
+ - {channel: 1, names: M-030, position: {az: -30.0, el: 0.0, r: 2.0 }}
+screen:
+ type: polar
+ aspectRatio: 1.78
+ centrePosition: {az: 0.0, el: 0.0, r: 1.0}
+ widthAzimuth: 58.0
+```
+
+A minimal example with a Cartesian screen would look like:
+
+```yaml
+speakers:
+ - {channel: 0, names: M+030, position: {az: 30.0, el: 0.0, r: 2.0 }}
+ - {channel: 1, names: M-030, position: {az: -30.0, el: 0.0, r: 2.0 }}
+screen:
+ type: cart
+ aspectRatio: 1.78
+ centrePosition: {X: 0.0, Y: 1.0, Z: 0.0}
+ widthX: 0.5
+```
+
+A minimal example with screen processing disabled:
+
+```yaml
+speakers:
+ - {channel: 0, names: M+030, position: {az: 30.0, el: 0.0, r: 2.0 }}
+ - {channel: 1, names: M-030, position: {az: -30.0, el: 0.0, r: 2.0 }}
+screen: null
+```
diff --git a/ear/__init__.py b/ear/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/cmdline/__init__.py b/ear/cmdline/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/cmdline/ambix_to_bwf.py b/ear/cmdline/ambix_to_bwf.py
new file mode 100644
index 00000000..e8de5c4c
--- /dev/null
+++ b/ear/cmdline/ambix_to_bwf.py
@@ -0,0 +1,146 @@
+import numpy as np
+import lxml.etree
+from ..core.hoa import from_acn
+from ..fileio.adm.adm import ADM
+from ..fileio.adm.elements import AudioBlockFormatHoa, AudioChannelFormat, TypeDefinition, FormatDefinition
+from ..fileio.adm.elements import AudioStreamFormat, AudioTrackFormat, AudioPackFormat, AudioObject, AudioTrackUID
+from ..fileio.adm.chna import populate_chna_chunk
+from ..fileio.adm.generate_ids import generate_ids
+from ..fileio.adm.xml import adm_to_xml
+from ..fileio import openBw64
+from ..fileio.bw64.chunks import ChnaChunk, FormatInfoChunk
+
+
+def add_args(subparsers):
+ subparser = subparsers.add_parser("ambix_to_bwf", help="make a BWF file from an ambix format HOA file")
+ subparser.add_argument("--norm", default="SN3D", help="normalization mode")
+ subparser.add_argument("--nfcDist", type=float, default=None, help="Near-Field Compensation Distance (float)")
+ subparser.add_argument("--screenRef", help="Screen Reference", action="store_true")
+ subparser.add_argument("--chna-only", help="use only CHNA with common definitions", action="store_true")
+ subparser.add_argument("input", help="input file")
+ subparser.add_argument("output", help="output BWF file")
+ subparser.set_defaults(command=ambix_to_bwf)
+
+
+def get_acn(n_channels, args):
+ return np.arange(n_channels)
+
+
+def build_adm(acn, norm, nfcDist, screenRef):
+ adm = ADM()
+
+ track_uids = []
+
+ pack_format = AudioPackFormat(
+ audioPackFormatName="HOA",
+ type=TypeDefinition.HOA,
+ audioChannelFormats=[],
+ )
+ adm.addAudioPackFormat(pack_format)
+
+ order, degree = from_acn(acn)
+ for channel_no, (order, degree) in enumerate(zip(order, degree), 1):
+ block_format = AudioBlockFormatHoa(
+ order=int(order),
+ degree=int(degree),
+ normalization=norm,
+ nfcRefDist=nfcDist,
+ screenRef=screenRef,
+ )
+
+ name = "channel_{}".format(channel_no)
+ channel_format = AudioChannelFormat(
+ audioChannelFormatName=name,
+ type=TypeDefinition.HOA,
+ audioBlockFormats=[block_format],
+ )
+ adm.addAudioChannelFormat(channel_format)
+ pack_format.audioChannelFormats.append(channel_format)
+
+ track_format = AudioTrackFormat(
+ audioTrackFormatName=name,
+ format=FormatDefinition.PCM,
+ )
+ adm.addAudioTrackFormat(track_format)
+
+ stream_format = AudioStreamFormat(
+ audioStreamFormatName=name,
+ format=FormatDefinition.PCM,
+ audioTrackFormats=[track_format],
+ audioChannelFormat=channel_format,
+ )
+ adm.addAudioStreamFormat(stream_format)
+
+ track_uid = AudioTrackUID(
+ trackIndex=channel_no,
+ audioTrackFormat=track_format,
+ audioPackFormat=pack_format,
+ )
+ adm.addAudioTrackUID(track_uid)
+ track_uids.append(track_uid)
+
+ audio_object = AudioObject(
+ audioObjectName="HOA",
+ audioPackFormats=[pack_format],
+ audioTrackUIDs=track_uids,
+ )
+ adm.addAudioObject(audio_object)
+
+ return adm
+
+
+def build_adm_common_defs(acns, norm):
+ from ..fileio.adm.common_definitions import load_common_definitions
+ adm = ADM()
+ load_common_definitions(adm)
+
+ order, degree = from_acn(acns)
+
+ pack_name = "3D_order{order}_{norm}_ACN".format(order=max(order), norm=norm)
+ [pack_format] = [apf for apf in adm.audioPackFormats if apf.audioPackFormatName == pack_name]
+
+ for channel_no, acn in enumerate(acns, 1):
+ track_name = "PCM_{norm}_ACN_{acn}".format(norm=norm, acn=acn)
+ [track_format] = [tf for tf in adm.audioTrackFormats if tf.audioTrackFormatName == track_name]
+
+ adm.addAudioTrackUID(AudioTrackUID(
+ trackIndex=channel_no,
+ audioTrackFormat=track_format,
+ audioPackFormat=pack_format,
+ ))
+
+ return adm
+
+
+def ambix_to_bwf(args):
+ with openBw64(args.input) as infile:
+ acn = get_acn(infile.channels, args)
+
+ if args.chna_only:
+ assert args.nfcDist is None
+ assert not args.screenRef
+ adm = build_adm_common_defs(acn, args.norm)
+ else:
+ adm = build_adm(acn, args.norm, args.nfcDist, args.screenRef)
+
+ generate_ids(adm)
+
+ if args.chna_only:
+ axml = None
+ else:
+ xml = adm_to_xml(adm)
+ axml = lxml.etree.tostring(xml, pretty_print=True)
+
+ chna = ChnaChunk()
+ populate_chna_chunk(chna, adm)
+
+ fmtInfo = FormatInfoChunk(formatTag=1,
+ channelCount=infile.channels,
+ sampleRate=infile.sampleRate,
+ bitsPerSample=infile.bitdepth)
+
+ with openBw64(args.output, 'w', chna=chna, formatInfo=fmtInfo, axml=axml) as outfile:
+ while True:
+ samples = infile.read(1024)
+ if samples.shape[0] == 0: break
+ outfile.write(samples)
diff --git a/ear/cmdline/dev_config.py b/ear/cmdline/dev_config.py
new file mode 100644
index 00000000..7a59aeb1
--- /dev/null
+++ b/ear/cmdline/dev_config.py
@@ -0,0 +1,19 @@
+def load_config(args):
+ from ruamel import yaml
+ if args.config is not None:
+ return yaml.safe_load(args.config)
+ else:
+ return {}
+
+
+def dump_config_command(args, config):
+ from ..core import Renderer
+ from .. import options
+ import sys
+ config_str = options.dump_config_with_comments(Renderer.options, options=config)
+
+ if args.output is None or args.output == "-":
+ sys.stdout.write(config_str)
+ else:
+ with open(args.output, "w") as f:
+ f.write(config_str)
diff --git a/ear/cmdline/generate_test_file.py b/ear/cmdline/generate_test_file.py
new file mode 100644
index 00000000..67b1a8df
--- /dev/null
+++ b/ear/cmdline/generate_test_file.py
@@ -0,0 +1,216 @@
+import ruamel.yaml
+import lxml.etree
+from ..core import layout
+from ..fileio.adm.builder import ADMBuilder
+from ..fileio.adm.elements import AudioBlockFormatObjects, JumpPosition, Frequency
+from ..fileio.adm.elements import AudioBlockFormatDirectSpeakers, BoundCoordinate, DirectSpeakerPolarPosition
+from ..fileio.adm.chna import populate_chna_chunk
+from ..fileio.adm.generate_ids import generate_ids
+from ..fileio.adm.xml import adm_to_xml
+from ..fileio import openBw64
+from ..fileio.bw64.chunks import ChnaChunk, FormatInfoChunk
+from fractions import Fraction
+
+"""
+Generate ADM BWF files from a simple yaml format, like this:
+
+ name: front centre
+ items:
+ - type: "Objects"
+ channels: [1]
+ blocks:
+ - rtime: 0.0
+ duration: 1.0
+ position:
+ azimuth: 0.0
+ elevation: 0.0
+ distance: 1.0
+ gain: 1.0
+ - type: "DirectSpeakers"
+ channels: [2]
+ blocks:
+ - speakerLabel: ["M+045"]
+ position:
+ azimuth:
+ value: 45.0
+ min: 30.0
+ max: 50.0
+ elevation: 0.0
+ - type: "DirectSpeakers"
+ channels: [3]
+ frequency:
+ lowPass: 120.0
+ blocks:
+ - speakerLabel: ["LFE1"]
+ position:
+ azimuth: 0.0
+ elevation: -30.0
+
+- items corresponds to the concept of 'rendering items' in the renderer, and
+ are a channel format associated with the channels that will be rendered with
+ that channel format.
+- block attributes should map to the attributes in the block format structures,
+ more will be added as needed
+- timing attributes are are parsed using Fraction, so may be numbers, decimal
+ format strings, or fraction format strings. Use quotes to make exact values!
+
+"""
+
+
+def load_frequency(item):
+ return Frequency(lowPass=item.get("frequency", {}).get("lowPass"),
+ highPass=item.get("frequency", {}).get("highPass"))
+
+
+def load_block_common(kwargs, block):
+ if "rtime" in block:
+ kwargs["rtime"] = Fraction(block["rtime"])
+ if "duration" in block:
+ kwargs["duration"] = Fraction(block["duration"])
+
+
+def load_jump_position(jumpPosition):
+ kwargs = {}
+
+ if "flag" in jumpPosition:
+ kwargs["flag"] = jumpPosition["flag"]
+
+ if "interpolationLength" in jumpPosition:
+ kwargs["interpolationLength"] = Fraction(jumpPosition["interpolationLength"])
+
+ return JumpPosition(**kwargs)
+
+
+def load_block_objects(block):
+ kwargs = {}
+ load_block_common(kwargs, block)
+
+ if "jumpPosition" in block:
+ kwargs["jumpPosition"] = load_jump_position(block["jumpPosition"])
+
+ for attr in ["position", "gain"]:
+ if attr in block:
+ kwargs[attr] = block[attr]
+
+ return AudioBlockFormatObjects(**kwargs)
+
+
+def create_item_objects(builder, item):
+ blocks = list(map(load_block_objects, item["blocks"]))
+
+ for channel in item["channels"]:
+ adm_item = builder.create_item_objects(name=item.get("name", "unnamed"),
+ track_index=channel - 1,
+ block_formats=blocks)
+ adm_item.channel_format.frequency = load_frequency(item)
+
+
+def load_block_direct_speakers(block):
+ kwargs = {}
+ load_block_common(kwargs, block)
+
+ if "speakerLabel" in block:
+ kwargs["speakerLabel"] = block["speakerLabel"]
+
+ def parse_bound(bound, default=None):
+ if isinstance(bound, dict):
+ return BoundCoordinate(value=bound["value"],
+ min=bound.get("min"),
+ max=bound.get("max"))
+ else:
+ return BoundCoordinate(value=bound)
+
+ pos_args = block["position"]
+ kwargs["position"] = DirectSpeakerPolarPosition(
+ bounded_azimuth=parse_bound(pos_args["azimuth"]),
+ bounded_elevation=parse_bound(pos_args["elevation"]),
+ bounded_distance=parse_bound(pos_args["distance"]) if "distance" in pos_args else BoundCoordinate(value=1.0),
+ )
+
+ return AudioBlockFormatDirectSpeakers(**kwargs)
+
+
+def create_item_direct_speakers(builder, item):
+ blocks = list(map(load_block_direct_speakers, item["blocks"]))
+
+ for channel in item["channels"]:
+ adm_item = builder.create_item_direct_speakers(name=item.get("name", "unnamed"),
+ track_index=channel - 1,
+ block_formats=blocks)
+ adm_item.channel_format.frequency = load_frequency(item)
+
+
+create_item_by_type = {
+ "Objects": create_item_objects,
+ "DirectSpeakers": create_item_direct_speakers,
+}
+
+
+def create_item(builder, item):
+ create_item_by_type[item["type"]](builder, item)
+
+
+def load_test_file_adm(filename):
+ with open(filename) as f:
+ yaml = ruamel.yaml.safe_load(f)
+
+ builder = ADMBuilder()
+
+ builder.create_programme(
+ audioProgrammeName=yaml.get("name", "unnamed"),
+ start=Fraction(yaml["start"]) if "start" in yaml else None,
+ end=Fraction(yaml["end"]) if "end" in yaml else None,
+ )
+
+ builder.create_content(
+ audioContentName="content",
+ )
+
+ for item in yaml["items"]:
+ create_item(builder, item)
+
+ return builder.adm
+
+
+def generate_test_file(test_path, in_wav_path, out_bwav_path, screen=None):
+ adm = load_test_file_adm(test_path)
+
+ if screen is not None:
+ adm.audioProgrammes[0].referenceScreen = screen
+
+ generate_ids(adm)
+
+ xml = adm_to_xml(adm)
+ axml = lxml.etree.tostring(xml, pretty_print=True)
+
+ chna = ChnaChunk()
+ populate_chna_chunk(chna, adm)
+
+ with openBw64(in_wav_path) as infile:
+ fmtInfo = FormatInfoChunk(formatTag=1,
+ channelCount=infile.channels,
+ sampleRate=infile.sampleRate,
+ bitsPerSample=infile.bitdepth)
+
+ with openBw64(out_bwav_path, 'w', chna=chna, formatInfo=fmtInfo, axml=axml) as outfile:
+ while True:
+ samples = infile.read(1024)
+ if samples.shape[0] == 0: break
+ outfile.write(samples)
+
+
+def make_test_bwf_command(args):
+ screen = layout.load_real_layout(args.screen).screen if args.screen is not None else None
+
+ generate_test_file(args.meta, args.input, args.output, screen=screen)
+
+
+def add_args(subparsers):
+ import argparse
+ subparser = subparsers.add_parser("make_test_bwf", help="make a bwf file from a wav file and some metadata")
+ subparser.add_argument("output", help="output bwf file")
+ subparser.add_argument("-i", "--input", help="input wav file", required=True)
+ subparser.add_argument("-m", "--meta", help="input yaml metadata file", required=True)
+ subparser.add_argument("--screen", type=argparse.FileType('r'), metavar="speakers_file",
+ help="YAML format speakers file to take reference screen from")
+ subparser.set_defaults(command=make_test_bwf_command)
diff --git a/ear/cmdline/render_file.py b/ear/cmdline/render_file.py
new file mode 100644
index 00000000..d74de0a4
--- /dev/null
+++ b/ear/cmdline/render_file.py
@@ -0,0 +1,118 @@
+from __future__ import print_function
+import argparse
+import sys
+import scipy.sparse
+from itertools import chain
+from ..core import bs2051, layout, Renderer
+from ..core.monitor import PeakMonitor
+from ..core.metadata_processing import preprocess_rendering_items
+from ..fileio import openBw64, openBw64Adm
+from ..fileio.bw64.chunks import FormatInfoChunk
+import warnings
+from ..fileio.adm.exceptions import AdmUnknownAttribute
+
+
+def handle_strict(args):
+ if args.strict:
+ warnings.filterwarnings("error", category=AdmUnknownAttribute)
+
+
+def process_file(input_file, output_file, target_layout, speakers_file, output_gain_db, fail_on_overload, enable_block_duration_fix, config=None):
+ if config is None:
+ config = {}
+
+ spkr_layout = bs2051.get_layout(target_layout)
+
+ if speakers_file is not None:
+ real_layout = layout.load_real_layout(speakers_file)
+ spkr_layout, upmix = spkr_layout.with_real_layout(real_layout)
+ spkr_layout.check_upmix_matrix(upmix)
+ upmix = scipy.sparse.csc_matrix(upmix.T)
+ n_channels = upmix.shape[1]
+ else:
+ upmix = None
+ n_channels = len(spkr_layout.channels)
+
+ renderer = Renderer(spkr_layout, **config)
+
+ output_gain_linear = 10.0 ** (output_gain_db / 20.0)
+
+ output_monitor = PeakMonitor(n_channels)
+
+ blocksize = 8192
+ with openBw64Adm(input_file, enable_block_duration_fix) as infile:
+ selected_items = preprocess_rendering_items(infile.selected_items)
+ renderer.set_rendering_items(selected_items)
+
+ formatInfo = FormatInfoChunk(formatTag=1,
+ channelCount=n_channels,
+ sampleRate=infile.sampleRate,
+ bitsPerSample=infile.bitdepth)
+ with openBw64(output_file, 'w', formatInfo=formatInfo) as outfile:
+ for input_samples in chain(infile.iter_sample_blocks(blocksize), [None]):
+ if input_samples is None:
+ output_samples = renderer.get_tail(infile.sampleRate, infile.channels)
+ else:
+ output_samples = renderer.render(infile.sampleRate, input_samples)
+
+ output_samples *= output_gain_linear
+
+ if upmix is not None:
+ output_samples *= upmix
+
+ output_monitor.process(output_samples)
+ outfile.write(output_samples)
+
+ output_monitor.warn_overloaded()
+ if fail_on_overload and output_monitor.has_overloaded():
+ sys.exit("error: output overloaded")
+
+
+def parse_command_line():
+ parser = argparse.ArgumentParser(description='EBU ADM renderer')
+
+ parser.add_argument('-d', '--debug',
+ help="print debug information when an error occurres",
+ action="store_true")
+
+ parser.add_argument('input_file')
+ parser.add_argument('output_file')
+
+ formats_string = ', '.join(bs2051.layout_names)
+ parser.add_argument('-s', '--system', required=True, metavar="target_system",
+ help='Target output system, accoring to ITU-R BS.2051. '
+ 'Available systems are: {}'.format(formats_string))
+
+ parser.add_argument('-l', '--layout', type=argparse.FileType('r'), metavar='layout_file',
+ help='Layout config file')
+ parser.add_argument('--output-gain-db', type=float, metavar='gain_db', default=0,
+ help='output gain in dB (default: 0)')
+ parser.add_argument('--fail-on-overload', "-c", action='store_true',
+ help='fail if an overload condition is detected in the output')
+ parser.add_argument('--enable-block-duration-fix', action='store_true',
+ help='automatically try to fix faulty block format durations')
+ parser.add_argument('--strict',
+ help="treat unknown ADM attributes as errors",
+ action="store_true")
+
+ args = parser.parse_args()
+ return args
+
+
+def main():
+ args = parse_command_line()
+
+ handle_strict(args)
+
+ try:
+ process_file(args.input_file, args.output_file, args.system, args.layout,
+ args.output_gain_db, args.fail_on_overload, args.enable_block_duration_fix)
+ except Exception as error:
+ if args.debug:
+ raise
+ else:
+ sys.exit(str(error))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ear/cmdline/utils.py b/ear/cmdline/utils.py
new file mode 100644
index 00000000..f21bc18c
--- /dev/null
+++ b/ear/cmdline/utils.py
@@ -0,0 +1,104 @@
+from __future__ import print_function
+import argparse
+import sys
+from ..compatibility import write_bytes_to_stdout
+from ..fileio import openBw64
+from ..fileio.bw64.chunks import FormatInfoChunk, ChnaChunk
+import warnings
+from . import ambix_to_bwf
+from . import generate_test_file
+
+
+def replace_axml_command(args):
+ from .fileio.adm import xml as adm_xml
+ from .fileio.adm import chna as adm_chna
+
+ with open(args.axml, 'rb') as axml_file:
+ axml = axml_file.read()
+
+ with openBw64(args.input) as infile:
+ formatInfo = FormatInfoChunk(channelCount=infile.channels,
+ sampleRate=infile.sampleRate,
+ bitsPerSample=infile.bitdepth)
+ if args.gen_chna:
+ adm = adm_xml.parse_string(axml)
+ adm_chna.guess_track_indices(adm)
+ chna = ChnaChunk()
+ else:
+ chna = infile.chna
+
+ if chna is None:
+ warnings.warn("No CHNA information available; output will " +
+ "not have a CHNA chunk. Either specify '-g', or use an input " +
+ "file with a CHNA chunk.")
+
+ with openBw64(args.output, 'w', formatInfo=formatInfo,
+ axml=axml, chna=infile.chna) as outfile:
+ while True:
+ samples = infile.read(2048)
+ if not len(samples):
+ break
+ outfile.write(samples)
+
+
+def dump_axml_command(args):
+ with openBw64(args.input) as infile:
+ write_bytes_to_stdout(infile.axml)
+
+ if sys.stdout.isatty() and not infile.axml.endswith(b"\n"):
+ sys.stdout.write("\n")
+
+
+def dump_chna_command(args):
+ with openBw64(args.input) as infile:
+ if args.binary:
+ sys.stdout.write(infile.get_chunk_data(b'chna'))
+ else:
+ for entry in infile.chna.audioIDs:
+ print(entry) # noqa
+
+
+def parse_command_line():
+ parser = argparse.ArgumentParser(description='EBU ADM renderer utilities')
+ subparsers = parser.add_subparsers(title='available subcommands')
+
+ def add_replace_axml_command():
+ subparser = subparsers.add_parser("replace_axml", help="replace the axml chunk in an existing ADM BWF file")
+ subparser.add_argument("input", help="input bwf file")
+ subparser.add_argument("output", help="output bwf file")
+ subparser.add_argument("-a", "--axml", help="new axml chunk file", required=True, metavar="file")
+ subparser.add_argument("-g", "--gen-chna", help="generate the CHNA information from the track UIDs", action="store_true")
+ subparser.set_defaults(command=replace_axml_command)
+
+ def add_dump_axml_command():
+ subparser = subparsers.add_parser("dump_axml", help="dump the axml chunk of an ADM BWF file to stdout")
+ subparser.add_argument("input", help="input bwf file")
+ subparser.set_defaults(command=dump_axml_command)
+
+ def add_dump_chna_command():
+ subparser = subparsers.add_parser("dump_chna", help="dump the chna chunk of an ADM BWF file to stdout")
+ subparser.add_argument("input", help="input bwf file")
+ subparser.add_argument("-b", "--binary", help="output binary data", action="store_true")
+ subparser.set_defaults(command=dump_chna_command)
+
+ generate_test_file.add_args(subparsers)
+ add_replace_axml_command()
+ add_dump_axml_command()
+ add_dump_chna_command()
+ ambix_to_bwf.add_args(subparsers)
+
+ args = parser.parse_args()
+ if 'command' not in args:
+ parser.error('No command specified')
+
+ return args
+
+
+def main():
+ args = parse_command_line()
+
+ args.command(args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ear/common.py b/ear/common.py
new file mode 100644
index 00000000..7c58f3cb
--- /dev/null
+++ b/ear/common.py
@@ -0,0 +1,203 @@
+from attr import attrs, attrib
+from attr.validators import instance_of
+import numpy as np
+
+
+def validate_range(minimum, maximum):
+ def f(instance, attribute, value):
+ if not (minimum <= value <= maximum):
+ raise ValueError('value "%s" out of range ( % s, % s)'
+ % (value, minimum, maximum))
+ return f
+
+
+def list_of(type):
+ """Attrs validator that checks for a list containing only a given type.
+
+ Parameters:
+ type: expected type of list items
+
+ Returns:
+ function: Validation function as required by the attr.attrib validator
+ argument.
+ """
+ list_validator = instance_of(list)
+
+ def f(inst, attr, value):
+ list_validator(inst, attr, value)
+
+ for item in value:
+ if not isinstance(item, type):
+ raise TypeError(
+ "'{name}' items must be {type!r} (got {item!r} that is a "
+ "{actual!r})."
+ .format(name=attr.name, type=type,
+ actual=item.__class__, item=item),
+ attr, type, item,
+ )
+ return f
+
+
+def cart(az, el, dist, axis=-1):
+ """Convert ADM-format polar positions to ADM-format Cartesian.
+
+ Parameters:
+ az: Azimuths in degrees, angle measured anticlockwise from front.
+ el: Elevations in degrees, angle measured up from equator.
+ r: Radii.
+ axis: Index of the new axis in the result; see `np.stack`. -1 (default)
+ adds a new axis at the end.
+
+ Returns:
+ ndarray: Same shape as broadcasting az, el and r together, with a new
+ axis at `axis` containing the X, Y and Z coordinates.
+
+ Examples:
+
+ >>> cart(0, 0, 1)
+ array([0., 1., 0.])
+ >>> cart(90, 0, 1).round(6)
+ array([-1., 0., 0.])
+ >>> cart(0, 90, 1).round(6)
+ array([0., 0., 1.])
+
+ # inputs are broadcast together...
+ >>> cart([0, 90], 0, 1).round(6)
+ array([[ 0., 1., 0.],
+ [-1., 0., 0.]])
+
+ # ... along the given axis
+ >>> cart([0, 90], 0, 1, axis=0).round(6)
+ array([[ 0., -1.],
+ [ 1., 0.],
+ [ 0., 0.]])
+ """
+ az, el, dist = np.broadcast_arrays(az, el, dist)
+
+ return np.stack((np.sin(np.radians(-az)) * np.cos(np.radians(el)) * dist,
+ np.cos(np.radians(-az)) * np.cos(np.radians(el)) * dist,
+ np.sin(np.radians(el)) * dist),
+ axis=axis)
+
+
+def azimuth(positions, axis=-1):
+ """Get the azimuth in degrees from ADM-format Cartesian positions.
+
+ Parameters:
+ positions (array of float): Cartesian positions, with X, Y and Z
+ positions along axis `axis`.
+ axis (int): Axis to find coordinates along. -1 (default) indicates the
+ last axis.
+
+ Returns:
+ array: Azimuths of the positions in degrees; has the same shape as
+ positions, with `axis` removed.
+
+ Raises:
+ ValueError: If positions does not have the right length along axis.
+
+ Examples:
+
+ >>> azimuth([0, 1, 0]).round(0).astype(int)
+ 0
+
+ >>> azimuth([[1, 0, 0], [0, 1, 0]]).round(0).astype(int)
+ array([-90, 0])
+
+ >>> azimuth([[1, 0], [0, 1], [0, 0]], axis=0).round(0).astype(int)
+ array([-90, 0])
+ """
+ x, y, z = np.moveaxis(positions, axis, 0)
+ return -np.degrees(np.arctan2(x, y))
+
+
+def elevation(positions, axis=-1):
+ """Get the azimuth in degrees from ADM-format Cartesian positions.
+
+ See `azimuth`.
+ """
+ x, y, z = np.moveaxis(positions, axis, 0)
+ radius = np.hypot(x, y)
+ return np.degrees(np.arctan2(z, radius))
+
+
+def distance(positions, axis=-1):
+ """Get the distance from ADM-format Cartesian positions.
+
+ See `azimuth`.
+ """
+ return np.linalg.norm(positions, axis=axis)
+
+
+class PolarPositionMixin(object):
+ """Methods to be defined on all polar position objects which have azimuth,
+ elevation and distance attributes."""
+ __slots__ = ()
+
+ def as_cartesian_array(self):
+ return cart(self.azimuth, self.elevation, self.distance)
+
+ def as_cartesian_position(self):
+ x, y, z = self.as_cartesian_array()
+ return CartesianPosition(x, y, z)
+
+ @property
+ def norm_position(self):
+ return cart(self.azimuth, self.elevation, 1.0)
+
+
+class CartesianPositionMixin(object):
+ """Methods to be defined on all Cartesian position objects which have X, Y
+ and Z attributes."""
+ __slots__ = ()
+
+ def as_cartesian_array(self):
+ return np.array([self.X, self.Y, self.Z])
+
+ def as_polar_position(self):
+ cart_array = self.as_cartesian_array()
+ return PolarPosition(azimuth(cart_array), elevation(cart_array), distance(cart_array))
+
+
+class Position(object):
+ """A 3D position represented in polar or Cartesian coordinates."""
+ __slots__ = ()
+
+
+@attrs(slots=True)
+class PolarPosition(Position, PolarPositionMixin):
+ """A 3D position represented in ADM-format polar coordinates."""
+ azimuth = attrib(convert=float, validator=validate_range(-180, 180))
+ elevation = attrib(convert=float, validator=validate_range(-90, 90))
+ distance = attrib(convert=float, validator=validate_range(0, float('inf')),
+ default=1.0)
+
+
+@attrs(slots=True)
+class CartesianPosition(Position, CartesianPositionMixin):
+ """A 3D position represented in ADM-format Cartesian coordinates."""
+ X = attrib(convert=float)
+ Y = attrib(convert=float)
+ Z = attrib(convert=float)
+
+
+@attrs(slots=True, frozen=True)
+class CartesianScreen(object):
+ aspectRatio = attrib(validator=instance_of(float))
+ centrePosition = attrib(validator=instance_of(CartesianPosition))
+ widthX = attrib(validator=instance_of(float))
+
+
+@attrs(slots=True, frozen=True)
+class PolarScreen(object):
+ aspectRatio = attrib(validator=instance_of(float))
+ centrePosition = attrib(validator=instance_of(PolarPosition))
+ widthAzimuth = attrib(validator=instance_of(float))
+
+
+default_screen = PolarScreen(aspectRatio=1.78,
+ centrePosition=PolarPosition(
+ azimuth=0.0,
+ elevation=0.0,
+ distance=1.0),
+ widthAzimuth=58.0)
diff --git a/ear/compatibility.py b/ear/compatibility.py
new file mode 100644
index 00000000..cc75e842
--- /dev/null
+++ b/ear/compatibility.py
@@ -0,0 +1,10 @@
+import sys
+from six import PY2
+
+
+def write_bytes_to_stdout(b):
+ """Write bytes (python 3) or string (python 2) to stdout."""
+ if PY2:
+ return sys.stdout.write(b)
+ else:
+ return sys.stdout.buffer.write(b)
diff --git a/ear/conftest.py b/ear/conftest.py
new file mode 100644
index 00000000..df321822
--- /dev/null
+++ b/ear/conftest.py
@@ -0,0 +1,7 @@
+import numpy as np
+import pytest
+
+
+@pytest.fixture(scope="session", autouse=True)
+def np_warnings_as_errors():
+ np.seterr(all="raise")
diff --git a/ear/core/__init__.py b/ear/core/__init__.py
new file mode 100644
index 00000000..136372f4
--- /dev/null
+++ b/ear/core/__init__.py
@@ -0,0 +1 @@
+from .renderer import Renderer # noqa: F401
diff --git a/ear/core/block_aligner.py b/ear/core/block_aligner.py
new file mode 100644
index 00000000..44b5c36e
--- /dev/null
+++ b/ear/core/block_aligner.py
@@ -0,0 +1,82 @@
+import numpy as np
+
+
+class BlockAligner(object):
+ """Mix a number of input streams with varying delays into a single aligned
+ output stream.
+
+ The calls to `add` and `get` must repeat in this sequence:
+
+ - one call to add with a block and delay for each input stream
+ - one call to get
+
+ Args:
+ n_channels (int): number of channels in all inputs and outputs.
+ """
+
+ def __init__(self, n_channels):
+ self.buf = np.zeros((0, n_channels))
+ # sample number of the first sample in the buffer
+ self.buf_start = 0
+ # sample number of the end of the earliest buffer added, or None if we
+ # are at the start of a round. This indicates the end of the completed
+ # region in buf.
+ self.first_end = None
+
+ def add(self, start, samples):
+ """Add a block of samples to be summed into the output.
+
+ Args:
+ start (int): index that the first sample in the block should take
+ in the output; may be negative.
+ samples (ndarray of n,k floats): n samples for k channels.
+ """
+ # strip off any samples before time 0
+ if start < self.buf_start:
+ assert self.buf_start == 0, "samples in past only allowed before time 0"
+
+ to_discard = min(self.buf_start - start, len(samples))
+ samples = samples[to_discard:]
+ start += to_discard
+ # here, we might have 0 samples, but we still go through the rest
+ # of the process so that first_end is updated below
+
+ end = start + len(samples)
+
+ # start and end indices of samples in self.buf
+ start_buf = start - self.buf_start
+ end_buf = end - self.buf_start
+
+ if end_buf > len(self.buf):
+ self.buf.resize((end_buf, self.buf.shape[1]))
+
+ if len(samples):
+ assert 0 <= start_buf and 0 < end_buf
+ self.buf[start_buf:end_buf] += samples
+
+ if self.first_end is None or self.first_end > end:
+ self.first_end = end
+
+ def get(self):
+ """Get the samples that have been completely filled by all input streams.
+
+ Returns:
+ ndarray of (n, k): n samples for k channels.
+
+ The number of samples returned varies according to the number of
+ input samples and their times, and may be 0. The first sample
+ returned is the sample for time 0.
+ """
+ assert self.first_end is not None
+ # number of samples that are completely filled and can be returned
+ n_samples = max(self.first_end - self.buf_start, 0)
+
+ # return the first n_samples samples, and shift the remaining samples to the start
+ to_return = self.buf[:n_samples].copy()
+ self.buf[:len(self.buf) - n_samples] = self.buf[n_samples:]
+ self.buf[len(self.buf) - n_samples:] = 0
+
+ self.buf_start += n_samples
+ self.first_end = None
+
+ return to_return
diff --git a/ear/core/bs2051.py b/ear/core/bs2051.py
new file mode 100644
index 00000000..0933e3f0
--- /dev/null
+++ b/ear/core/bs2051.py
@@ -0,0 +1,63 @@
+import pkg_resources
+from ruamel import yaml
+from .geom import PolarPosition
+from .layout import Channel, Layout
+
+
+def _dict_to_channel(d):
+ position = PolarPosition(azimuth=d["position"]["az"],
+ elevation=d["position"]["el"],
+ distance=1.0)
+
+ return Channel(
+ name=d["name"],
+ is_lfe=d.get("is_lfe", False),
+ polar_position=position,
+ polar_nominal_position=position,
+ az_range=tuple(d.get("az_range", (position.azimuth, position.azimuth))),
+ el_range=tuple(d.get("el_range", (position.elevation, position.elevation))),
+ )
+
+
+def _dict_to_layout(d):
+ return Layout(
+ name=d["name"],
+ channels=list(map(_dict_to_channel, d["channels"])),
+ )
+
+
+def _load_layouts():
+ fname = "data/2051_layouts.yaml"
+ with pkg_resources.resource_stream(__name__, fname) as layouts_file:
+ layouts_data = yaml.safe_load(layouts_file)
+
+ layouts = list(map(_dict_to_layout, layouts_data))
+
+ for layout in layouts:
+ errors = []
+ layout.check_positions(callback=errors.append)
+ assert errors == []
+
+ layout_names = [layout.name for layout in layouts]
+ layouts_dict = {layout.name: layout for layout in layouts}
+
+ return layout_names, layouts_dict
+
+
+layout_names, layouts = _load_layouts()
+
+
+def get_layout(name):
+ """Get data for a layout specified in BS.2051.
+
+ Parameters:
+ name: Full layout name, e.g. "4+5+0"
+
+ Returns:
+ Layout object representing the layout; real speaker positions are set
+ to the nominal positions.
+ """
+ if name not in layout_names:
+ raise KeyError("Unknown layout name '{name}'.".format(name=name))
+
+ return layouts[name]
diff --git a/ear/core/convolver.py b/ear/core/convolver.py
new file mode 100644
index 00000000..0d98cd89
--- /dev/null
+++ b/ear/core/convolver.py
@@ -0,0 +1,128 @@
+import numpy as np
+
+
+class OverlapSaveConvolver(object):
+ """Objects that convolve a signal with a filter in fixed-size blocks.
+
+ Parameters:
+ block_size (int): time domain block size for input and output blocks
+ nchannels (int): number of channels to process
+ f (array of (n, nchannels) floats): specification of nchannels length n
+ FIR filters to convolve the input channels with.
+
+ Attributes:
+ block_size (int): time domain block size for input and output blocks
+ filter_blocks_fd (list of complex arrays): blocks of block_size samples
+ of the filter, padded to 2*block_size and fft-ed
+ blocks_fd (list of complex arrays): The filter state; blocks_fd[i] will
+ form the output in i blocks time, so each input block is multiplied
+ by filter_blocks_fd[i] and summed into blocks_fd[i]. After each
+ block this queue is rotated to maintain this invariant.
+ input_block (array of (block_size*2, nchannels) floats): input to the
+ forward fft; the first half contains the input for this block, and the
+ second half contains the input from the previous block, so that the
+ first half of each block in blocks_fd will contain the tail from the
+ previous block.
+ """
+
+ def __init__(self, block_size, nchannels, f):
+ self.block_size = block_size
+ self.input_block = np.zeros((block_size * 2, nchannels))
+
+ self.filter_blocks_fd = []
+ self.blocks_fd = []
+ for start in range(0, len(f), self.block_size):
+ end = min(len(f), start + self.block_size)
+ block_fd = np.fft.rfft(f[start:end], self.block_size * 2, axis=0)
+
+ self.filter_blocks_fd.append(block_fd)
+ self.blocks_fd.append(np.zeros_like(block_fd))
+
+ def filter_block(self, in_block_td):
+ """Filter a time domain block of samples.
+
+ Parameters:
+ in_block_td (array of (block_size, nchannels) floats): block of
+ time domain input samples
+
+ Returns:
+ array of (block_size, nchannels) floats: block of time domain
+ output samples
+ """
+ self.input_block[self.block_size:] = self.input_block[:self.block_size]
+ self.input_block[:self.block_size] = in_block_td
+
+ in_block_fd = np.fft.rfft(self.input_block, axis=0)
+
+ for filter_block, block in zip(self.filter_blocks_fd, self.blocks_fd):
+ block += filter_block * in_block_fd
+
+ first_block_td = np.fft.irfft(self.blocks_fd[0], axis=0)
+
+ self.blocks_fd[0][:] = 0.0
+ self.blocks_fd.append(self.blocks_fd.pop(0))
+
+ return first_block_td[:self.block_size]
+
+
+class VariableBlockSizeAdapter(object):
+ """Adapt a block that processes fixed-size blocks of samples into one that
+ processes variable sized blocks by adding some delay.
+
+ Parameters:
+ block_size (int): Block size that process_func accepts.
+ nchannels (int): Number of channels that block_size accepts.
+ process_func (callable): Callback such that Y=process_func(X) processes
+ an (block_size, nchannels) array X, to produce another (block_size,
+ nchannels) array Y.
+ """
+
+ def __init__(self, block_size, nchannels, process_func):
+ self.process_func = process_func
+ self.block_size = block_size
+
+ # store block_size samples, input samples followed by output samples:
+ # - self.buffer[:self.buffer_input] stores unprocessed input samples
+ # - self.buffer[self.buffer_input:] stores processed output samples
+ self.buffer = process_func(np.zeros((block_size, nchannels)))
+ self.buffer_input = 0
+
+ def delay(self, process_delay):
+ return self.block_size + process_delay
+
+ def process(self, input_samples):
+ """Process n samples.
+
+ Parameters:
+ input_samples (array of (n, nchannels) floats): input samples
+
+ Returns:
+ array of (n, nchannels) floats: output samples
+ """
+ output_samples = np.zeros_like(input_samples)
+
+ # range of input and output samples that are yet to be processed
+ n_done, n_input = 0, len(input_samples)
+
+ while n_done < n_input:
+ # transfer as many samples as possible from the buffer to the
+ # output, and from the input to the buffer in their place.
+ to_xfer = min(n_input - n_done, self.block_size - self.buffer_input)
+ buffer_slice = slice(self.buffer_input, self.buffer_input+to_xfer)
+ samples_slice = slice(n_done, n_done + to_xfer)
+
+ output_samples[samples_slice] = self.buffer[buffer_slice]
+ self.buffer[buffer_slice] = input_samples[samples_slice]
+
+ self.buffer_input += to_xfer
+ n_done += to_xfer
+
+ # at this point the buffer is a full as it can be of input samples;
+ # process these to turn them into output samples if we have enough
+ if self.buffer_input == self.block_size:
+ self.buffer[:] = self.process_func(self.buffer)
+ self.buffer_input = 0
+
+ assert n_done == n_input
+
+ return output_samples
diff --git a/ear/core/data/2051_layouts.yaml b/ear/core/data/2051_layouts.yaml
new file mode 100644
index 00000000..5e3f7017
--- /dev/null
+++ b/ear/core/data/2051_layouts.yaml
@@ -0,0 +1,378 @@
+- name: 0+2+0
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+- name: 0+5+0
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0} # XXX: LFE does not have a position
+ az_range: [-180.0, 180.0]
+ el_range: [-90.0, 90.0]
+ - name: M+110
+ position: {az: 110.0, el: 0.0}
+ az_range: [100.0, 120.0]
+ el_range: [0.0, 15.0]
+ - name: M-110
+ position: {az: -110.0, el: 0.0}
+ az_range: [-120.0, -100.0]
+ el_range: [0.0, 15.0]
+- name: 2+5+0
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0} # XXX: LFE does not have a position
+ az_range: [-180.0, 180.0]
+ el_range: [-90.0, 90.0]
+ - name: M+110
+ position: {az: 110.0, el: 0.0}
+ az_range: [100.0, 120.0]
+ el_range: [0.0, 15.0]
+ - name: M-110
+ position: {az: -110.0, el: 0.0}
+ az_range: [-120.0, -100.0]
+ el_range: [0.0, 15.0]
+ - name: U+030
+ position: {az: 30.0, el: 30.0}
+ az_range: [30.0, 45.0]
+ el_range: [30.0, 55.0]
+ - name: U-030
+ position: {az: -30.0, el: 30.0}
+ az_range: [-45.0, -30.0]
+ el_range: [30.0, 55.0]
+- name: 4+5+0
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0} # XXX: LFE does not have a position
+ az_range: [-180.0, 180.0]
+ el_range: [-90.0, 90.0]
+ - name: M+110
+ position: {az: 110.0, el: 0.0}
+ az_range: [100.0, 120.0]
+ - name: M-110
+ position: {az: -110.0, el: 0.0}
+ az_range: [-120.0, -100.0]
+ - name: U+030
+ position: {az: 30.0, el: 30.0}
+ az_range: [30.0, 45.0]
+ el_range: [30.0, 55.0]
+ - name: U-030
+ position: {az: -30.0, el: 30.0}
+ az_range: [-45.0, -30.0]
+ el_range: [30.0, 55.0]
+ - name: U+110
+ position: {az: 110.0, el: 30.0}
+ az_range: [100.0, 135.0]
+ el_range: [30.0, 55.0]
+ - name: U-110
+ position: {az: -110.0, el: 30.0}
+ az_range: [-135.0, -100.0]
+ el_range: [30.0, 55.0]
+- name: 4+5+1
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0} # XXX: LFE does not have a position
+ az_range: [-180.0, 180.0]
+ el_range: [-90.0, 90.0]
+ - name: M+110
+ position: {az: 110.0, el: 0.0}
+ az_range: [100.0, 120.0]
+ - name: M-110
+ position: {az: -110.0, el: 0.0}
+ az_range: [-120.0, -100.0]
+ - name: U+030
+ position: {az: 30.0, el: 30.0}
+ az_range: [30.0, 45.0]
+ el_range: [30.0, 55.0]
+ - name: U-030
+ position: {az: -30.0, el: 30.0}
+ az_range: [-45.0, -30.0]
+ el_range: [30.0, 55.0]
+ - name: U+110
+ position: {az: 110.0, el: 30.0}
+ az_range: [100.0, 135.0]
+ el_range: [30.0, 55.0]
+ - name: U-110
+ position: {az: -110.0, el: 30.0}
+ az_range: [-135.0, -100.0]
+ el_range: [30.0, 55.0]
+ - name: B+000
+ position: {az: 0.0, el: -30.0}
+ el_range: [-30.0, -15.0]
+- name: 3+7+0
+ channels:
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ - name: U+045
+ position: {az: 45.0, el: 30.0}
+ az_range: [30.0, 45.0]
+ el_range: [30.0, 45.0]
+ - name: U-045
+ position: {az: -45.0, el: 30.0}
+ az_range: [-45.0, -30.0]
+ el_range: [30.0, 45.0]
+ - name: M+090
+ position: {az: 90.0, el: 0.0}
+ az_range: [60.0, 150.0]
+ - name: M-090
+ position: {az: -90.0, el: 0.0}
+ az_range: [-150.0, -60.0]
+ - name: M+135
+ position: {az: 135.0, el: 0.0}
+ az_range: [60.0, 150.0]
+ - name: M-135
+ position: {az: -135.0, el: 0.0}
+ az_range: [-150.0, -60.0]
+ - name: UH+180
+ position: {az: 180.0, el: 45.0}
+ el_range: [45.0, 90.0]
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0}
+ az_range: [30.0, 90.0]
+ el_range: [-30.0, -15.0]
+ - name: LFE2
+ is_lfe: true
+ position: {az: -45.0, el: -30.0}
+ az_range: [-90.0, -30.0]
+ el_range: [-30.0, -15.0]
+- name: 4+9+0
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ az_range: [30.0, 45.0]
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ az_range: [-45.0, -30.0]
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0} # XXX: LFE does not have a position
+ az_range: [-180.0, 180.0]
+ el_range: [-90.0, 90.0]
+ - name: M+090
+ position: {az: 90.0, el: 0.0}
+ az_range: [85.0, 110.0]
+ - name: M-090
+ position: {az: -90.0, el: 0.0}
+ az_range: [-110.0, -85.0]
+ - name: M+135
+ position: {az: 135.0, el: 0.0}
+ az_range: [120.0, 150.0]
+ - name: M-135
+ position: {az: -135.0, el: 0.0}
+ az_range: [-150.0, -120.0]
+ - name: U+045
+ position: {az: 45.0, el: 30.0}
+ az_range: [30.0, 45.0]
+ el_range: [30.0, 55.0]
+ - name: U-045
+ position: {az: -45.0, el: 30.0}
+ az_range: [-45.0, -30.0]
+ el_range: [30.0, 55.0]
+ - name: U+135
+ position: {az: 135.0, el: 30.0}
+ az_range: [100.0, 150.0]
+ el_range: [30.0, 55.0]
+ - name: U-135
+ position: {az: -135.0, el: 30.0}
+ az_range: [-150.0, -100.0]
+ el_range: [30.0, 55.0]
+ - name: M+SC
+ position: {az: 20.0, el: 0.0} # XXX: M+SC does not have a specified position
+ az_range: [-180, 180]
+ - name: M-SC
+ position: {az: -20.0, el: 0.0} # XXX: M-SC does not have a specified position
+ az_range: [-180, 180]
+- name: 9+10+3
+ channels:
+ - name: M+060
+ position: {az: 60.0, el: 0.0}
+ az_range: [45.0, 60.0]
+ el_range: [0.0, 5.0]
+ - name: M-060
+ position: {az: -60.0, el: 0.0}
+ az_range: [-60.0, -45.0]
+ el_range: [0.0, 5.0]
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ el_range: [0.0, 5.0]
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0}
+ az_range: [30.0, 90.0]
+ el_range: [-30.0, -15.0]
+ - name: M+135
+ position: {az: 135.0, el: 0.0}
+ az_range: [110.0, 135.0]
+ el_range: [0.0, 15.0]
+ - name: M-135
+ position: {az: -135.0, el: 0.0}
+ az_range: [-135.0, -110.0]
+ el_range: [0.0, 15.0]
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ az_range: [22.5, 30.0]
+ el_range: [0.0, 5.0]
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ az_range: [-30.0, -22.5]
+ el_range: [0.0, 5.0]
+ - name: M+180
+ position: {az: 180.0, el: 0.0}
+ el_range: [0.0, 15.0]
+ - name: LFE2
+ is_lfe: true
+ position: {az: -45.0, el: -30.0}
+ az_range: [-90.0, -30.0]
+ el_range: [-30.0, -15.0]
+ - name: M+090
+ position: {az: 90.0, el: 0.0}
+ el_range: [0.0, 15.0]
+ - name: M-090
+ position: {az: -90.0, el: 0.0}
+ el_range: [0.0, 15.0]
+ - name: U+045
+ position: {az: 45.0, el: 30.0}
+ az_range: [45.0, 60.0]
+ el_range: [30.0, 45.0]
+ - name: U-045
+ position: {az: -45.0, el: 30.0}
+ az_range: [-60.0, -45.0]
+ el_range: [30.0, 45.0]
+ - name: U+000
+ position: {az: 0.0, el: 30.0}
+ el_range: [30.0, 45.0]
+ - name: T+000
+ position: {az: 0.0, el: 90.0}
+ - name: U+135
+ position: {az: 135.0, el: 30.0}
+ az_range: [110.0, 135.0]
+ el_range: [30.0, 45.0]
+ - name: U-135
+ position: {az: -135.0, el: 30.0}
+ az_range: [-135.0, -110.0]
+ el_range: [30.0, 45.0]
+ - name: U+090
+ position: {az: 90.0, el: 30.0}
+ el_range: [30.0, 45.0]
+ - name: U-090
+ position: {az: -90.0, el: 30.0}
+ el_range: [30.0, 45.0]
+ - name: U+180
+ position: {az: 180.0, el: 30.0}
+ el_range: [30.0, 45.0]
+ - name: B+000
+ position: {az: 0.0, el: -30.0}
+ el_range: [-30.0, -15.0]
+ - name: B+045
+ position: {az: 45.0, el: -30.0}
+ az_range: [45.0, 60.0]
+ el_range: [-30.0, -15.0]
+ - name: B-045
+ position: {az: -45.0, el: -30.0}
+ az_range: [-60.0, -45.0]
+ el_range: [-30.0, -15.0]
+- name: 0+7+0
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ az_range: [30.0, 45.0]
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ az_range: [-45.0, -30.0]
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0} # XXX: LFE does not have a position
+ az_range: [-180.0, 180.0]
+ el_range: [-90.0, 90.0]
+ - name: M+090
+ position: {az: 90.0, el: 0.0}
+ az_range: [85.0, 110.0]
+ - name: M-090
+ position: {az: -90.0, el: 0.0}
+ az_range: [-110.0, -85.0]
+ - name: M+135
+ position: {az: 135.0, el: 0.0}
+ az_range: [120.0, 150.0]
+ - name: M-135
+ position: {az: -135.0, el: 0.0}
+ az_range: [-150.0, -120.0]
+- name: 4+7+0
+ channels:
+ - name: M+030
+ position: {az: 30.0, el: 0.0}
+ az_range: [30.0, 45.0]
+ - name: M-030
+ position: {az: -30.0, el: 0.0}
+ az_range: [-45.0, -30.0]
+ - name: M+000
+ position: {az: 0.0, el: 0.0}
+ - name: LFE1
+ is_lfe: true
+ position: {az: 45.0, el: -30.0} # XXX: LFE does not have a position
+ az_range: [-180.0, 180.0]
+ el_range: [-90.0, 90.0]
+ - name: M+090
+ position: {az: 90.0, el: 0.0}
+ az_range: [85.0, 110.0]
+ - name: M-090
+ position: {az: -90.0, el: 0.0}
+ az_range: [-110.0, -85.0]
+ - name: M+135
+ position: {az: 135.0, el: 0.0}
+ az_range: [120.0, 150.0]
+ - name: M-135
+ position: {az: -135.0, el: 0.0}
+ az_range: [-150.0, -120.0]
+ - name: U+045
+ position: {az: 45.0, el: 30.0}
+ az_range: [30.0, 45.0]
+ el_range: [30.0, 55.0]
+ - name: U-045
+ position: {az: -45.0, el: 30.0}
+ az_range: [-45.0, -30.0]
+ el_range: [30.0, 55.0]
+ - name: U+135
+ position: {az: 135.0, el: 30.0}
+ az_range: [100.0, 150.0]
+ el_range: [30.0, 55.0]
+ - name: U-135
+ position: {az: -135.0, el: 30.0}
+ az_range: [-150.0, -100.0]
+ el_range: [30.0, 55.0]
diff --git a/ear/core/data/Design_5200_100_random.dat b/ear/core/data/Design_5200_100_random.dat
new file mode 100644
index 00000000..79aa7839
--- /dev/null
+++ b/ear/core/data/Design_5200_100_random.dat
@@ -0,0 +1,5200 @@
+1.5792370633651345e+00 1.0304969346233341e+00
+4.1381472401298893e+00 8.5921502052710486e-01
+2.0065031144956338e+00 8.7331972533438851e-01
+6.3628159134217110e-01 1.3700277816438764e+00
+3.3008257781610748e+00 1.9241288711937727e+00
+9.9604784296503546e-01 1.0555759210657527e+00
+5.5478618448724077e+00 1.5792230787745893e+00
+4.2521032174118965e+00 1.5517536583388492e+00
+5.3425133806717398e+00 1.6273902200121355e+00
+4.8067599563599614e+00 1.7590367462033480e+00
+5.0574238821576794e+00 2.2769570104496624e+00
+4.0100538281159839e+00 2.1007371933829133e+00
+4.5217023022892660e+00 6.5798669434346158e-01
+4.4033559928942489e+00 7.2837049834212297e-01
+2.0650336204233057e+00 1.5843498454508886e+00
+3.2826824670870067e+00 1.3349748889654263e+00
+5.4240203866414864e+00 1.1707167382444563e+00
+3.1427565998717677e-01 2.3213952678596641e+00
+3.0954901890124020e+00 1.4196091771319785e+00
+2.7661652306691060e+00 2.1738749742554351e+00
+5.7319394243143282e+00 2.6252358814317329e+00
+4.0252107363539142e+00 2.6114278592565956e+00
+6.1002733790764188e+00 4.7305750867072183e-01
+3.6877689107220051e+00 1.5747989472114563e+00
+5.3040021889796538e+00 2.3393409675997745e+00
+2.9569542533362538e+00 1.2779177610498320e+00
+3.3759847275731394e+00 1.4300522290472986e+00
+1.1077322114717414e+00 1.9742277261847117e+00
+4.0216599653433747e+00 1.1642199743520274e+00
+6.0912223889257859e+00 1.7741198839968917e+00
+3.3308791803719711e+00 2.1719127769765212e+00
+2.9528409453586204e+00 2.1049756155784642e+00
+4.9982238996856738e+00 1.7987542936945395e+00
+5.9619479596149638e-01 2.7987888837941259e+00
+5.5226086745218108e+00 1.8427729521143439e+00
+6.2333260033356730e+00 1.4802954559151700e+00
+3.1946461201616239e+00 1.4775167845265740e+00
+4.3324738174838107e+00 2.6125325332140958e+00
+3.5320997472995179e+00 1.4083928234203849e+00
+2.9955378648893194e+00 1.6105457568546395e+00
+2.0175006609346964e+00 1.2612326060641477e+00
+3.6940028408728751e+00 9.2041485416248803e-01
+5.7176863506397613e+00 2.2589013173696824e+00
+4.2230422145627280e+00 3.7592465730953917e-01
+5.9580365036668228e+00 1.4767419072037624e+00
+5.7679586068738331e-01 5.5873397440778660e-01
+3.1802545319128241e+00 1.2929736883661422e+00
+6.9965858850834550e-01 2.2873109970668795e+00
+3.4234753111243226e+00 2.6524623423075204e+00
+4.4696674200039670e+00 2.8907672393293917e+00
+9.7697050982439571e-01 2.2101604654863967e+00
+4.8992333155238130e+00 1.5659799287842817e+00
+2.5542554193788329e+00 2.3630695610352497e+00
+5.6775729291237393e+00 2.5851188171335409e+00
+1.9232026493455292e+00 1.4176362878458397e+00
+3.0142974917217824e-01 2.5207042950118330e-01
+1.3692154245508024e+00 2.3262188710605565e+00
+5.5762752209896860e-01 2.2991075345654930e+00
+6.0133140066942268e+00 2.2814442841867657e+00
+5.3012668208106017e-02 2.5708459026768269e+00
+7.2017668422666414e-01 1.1037486422002010e+00
+8.5542003466163585e-03 1.6359044795739406e+00
+1.3397496564460394e+00 1.5710528653912939e+00
+4.6824741849086582e-02 1.3775034890957147e+00
+4.1006535917883458e+00 6.2213505243946099e-01
+3.1767329689196897e+00 2.3844697480391286e+00
+1.5248111429571225e+00 2.0395629924432432e+00
+1.2941058155248044e+00 2.5988007673570612e+00
+4.7883051869214555e-01 2.3307107497014208e+00
+2.3320729902875494e+00 2.5477084797864720e+00
+8.8136561454795537e-02 2.2687563101780226e+00
+3.7263975041095678e+00 1.0066859889708248e+00
+3.0226000278132492e+00 6.8566701337864400e-01
+1.9539654175469070e+00 1.5392837311530281e+00
+4.6831706999647622e+00 7.5376743057740014e-01
+5.2824259443962296e+00 2.1616937264548639e+00
+1.6987966076255643e+00 1.7094223585763455e+00
+3.1537697294348197e+00 1.8012448400494334e+00
+2.8038709350458171e+00 1.1654950464651921e+00
+2.1554672562791821e+00 4.4337161798217073e-01
+5.2696811746163217e+00 2.2089552463782112e+00
+6.1652596592103244e+00 1.0128018254568010e+00
+3.8788641404286208e+00 1.5568124908836236e+00
+1.0835584749963658e+00 1.0625062126878420e+00
+7.6867327118229367e-01 7.0176193876735249e-01
+3.6096664164468790e+00 2.1137713529492412e+00
+2.0031578373799812e+00 4.4884989150030319e-01
+1.5766950943123654e+00 2.8442747178254639e+00
+3.4221448742781262e+00 1.4413741930311128e+00
+1.2091143153496373e+00 6.8718605567516544e-01
+4.3234232024218766e+00 2.3388833434472813e+00
+3.5128467807424801e-01 1.7374222466206508e+00
+2.1602503136892715e-01 1.9229745823707138e+00
+1.8146725686737810e+00 1.4476726743351598e+00
+4.3377679680227521e-01 8.2545374372292091e-01
+5.6063182642736518e+00 1.7685795216700311e+00
+5.2966890006379792e+00 1.2927056969143744e+00
+2.3678370301099090e+00 3.4843374998524834e-01
+1.0648105182449075e+00 8.4535132837663962e-01
+2.7487287605724107e+00 9.1274622626394619e-01
+2.6773235755460436e+00 4.4243130872663583e-01
+4.6051660228941369e+00 2.2735692147797160e+00
+2.5861871163066867e+00 1.2594201608863531e+00
+5.9893329590722164e+00 1.0501329083545650e+00
+5.7220653369965060e+00 8.7178256848588775e-01
+5.9411901646895808e+00 1.1804427185711137e+00
+2.2381261489737301e+00 1.8859163479070404e+00
+1.8059724679457725e+00 2.7347167275203663e+00
+5.4843659700574108e+00 2.4307878366122915e+00
+1.2937580237186848e+00 2.0280967671320571e+00
+8.1767041157878406e-01 8.1859924569505171e-01
+3.2656029344659325e+00 6.5262413566262101e-01
+5.6882381376428395e+00 1.7074945833744528e+00
+2.3727189696599140e+00 1.8299268848327999e-01
+1.3684030899742763e+00 2.7480788213339875e+00
+4.4424246028169678e-01 7.6830810801749283e-01
+5.8566444451831892e+00 1.4946087720334404e+00
+3.8500559660484655e+00 3.1850284446744137e-01
+2.3930671779161776e+00 7.5577740488179168e-01
+4.2354931048525764e+00 2.3495512129777607e+00
+4.9670066658150400e+00 1.8454113410734903e+00
+2.1703257371226128e+00 2.6439903049263123e+00
+4.4286498450092973e+00 1.7133294209111292e+00
+1.2826596825037269e+00 1.7117644324516135e+00
+6.0168493906136735e+00 1.9533746972251493e+00
+4.4310844940885907e+00 5.4883839220823760e-01
+1.0487706980177816e+00 1.5320971950549522e+00
+2.7616899882325452e+00 1.1381296313720302e+00
+3.9930926990404685e+00 1.2397243197456551e+00
+5.8591923778918895e+00 8.3037564988988999e-01
+3.2572421656634454e+00 2.0493201433141679e+00
+3.9570132562771443e+00 9.3585739407702873e-01
+4.3006521385587515e+00 1.7523852312574135e+00
+5.7937140770891098e+00 2.6974293146089834e+00
+9.8379663851775323e-01 7.0413575371843740e-01
+2.4966738886371482e+00 3.1560624455383479e-01
+1.8520142550031040e+00 2.4938342756546148e+00
+4.4244708036015723e+00 1.5114396012300648e+00
+5.5966092357409503e+00 1.0952822596096143e+00
+3.0271283678192074e+00 2.4220899502083419e+00
+5.0970544374792368e+00 2.7579109075785642e+00
+2.0863451726601649e+00 1.7253563675391952e+00
+3.3889313784185200e+00 2.4022184922500611e+00
+2.2697053974656702e+00 3.0966015079296638e-01
+5.5773446167752425e+00 4.9540611896777298e-01
+4.2823545554051119e+00 4.7358208258098022e-01
+5.1000342404564281e+00 2.7599268424218670e-01
+7.0186410130339727e-01 1.1517125754571778e+00
+1.7920450411334945e+00 1.1196976286036850e+00
+4.8633704399026332e+00 1.8382018912917160e+00
+1.4020999768861244e+00 2.5988146384524935e+00
+1.8753801972125439e-01 1.6625129522551203e+00
+2.6877488121998123e+00 1.5060334586261004e+00
+5.8861226953430066e+00 1.4585499830179423e+00
+1.6658513427286927e+00 9.3165163699609543e-01
+3.5363305946286583e+00 1.3414705280712926e+00
+2.2951663760896692e+00 4.6032143701767558e-01
+1.4641587373348042e-01 1.3266896842484364e+00
+3.1772530331931530e+00 7.2352461326106643e-01
+5.1918901625350538e+00 2.3457444275741999e+00
+1.6677329788351809e+00 8.7759439967250763e-01
+1.8547217250234027e-01 3.2864816137357566e-01
+1.5163952714755187e+00 2.1859093335722219e+00
+4.1780835979830790e+00 5.3550179642198192e-01
+2.1072008693314919e+00 1.2159853610898446e+00
+4.1865652966573057e+00 1.0971367006785540e+00
+1.0436672426009430e-02 1.1613620906224786e+00
+4.5437766682224385e+00 9.0751324088942731e-01
+2.4935915877023085e+00 1.5119359543737356e+00
+2.3257131688675026e-01 9.7335667295344164e-01
+2.5712392143976430e+00 1.6423778375123033e+00
+4.5059659597641710e+00 5.8237019903257403e-01
+5.7779019252241088e+00 2.3840005927781585e+00
+6.2124217370607049e+00 7.5728367019899345e-01
+6.1428638911237687e+00 2.3001332704852766e+00
+5.5633224059586430e+00 1.0073923043324127e+00
+5.4431441322362284e+00 1.6546144213896634e+00
+5.0995117675590897e+00 1.3567451997525490e+00
+3.4569285196599924e+00 1.8286686522269759e+00
+2.6569586686441955e+00 1.7460653152926850e+00
+7.9505873597630794e-01 1.0496064712217523e+00
+4.0476431063336991e+00 4.1268747540741990e-01
+5.3478617774574779e+00 7.1181528532592209e-01
+1.6988459312355377e+00 1.3018790994613796e+00
+5.2665685317592503e+00 3.7139673127127582e-01
+4.1969753489312556e-01 1.3375154040754293e+00
+2.3923283722786168e+00 9.8508484170722677e-01
+1.6866090786648475e+00 6.6868708064160742e-01
+9.5406774058934030e-01 2.1555478176663212e+00
+3.9923658444326131e+00 2.4981937894522197e+00
+1.9754249299947646e+00 8.1763215957489410e-01
+6.0768045185130468e+00 1.7225532816268061e+00
+3.1438853227626673e+00 1.0944071818705114e+00
+4.6478402881175214e+00 5.1608544086890840e-01
+7.3709478733070100e-02 2.0478735424430710e+00
+3.7636077886245300e+00 1.9315820460844835e+00
+3.6298227553173459e+00 1.4847700032379056e+00
+5.1215835403207084e+00 1.4654194939613596e+00
+4.0992364426464540e+00 1.2228332708261762e+00
+5.5317391627827242e+00 1.3374316948153151e+00
+5.6761684211175769e+00 2.3131728642517650e+00
+9.3148420552035249e-01 1.7610643399710235e+00
+1.1982875665252453e+00 1.7875134593916884e+00
+4.9582251349811273e+00 2.5323439085452089e+00
+4.2355875709322532e-01 2.1177406701606118e+00
+2.3341200051126343e+00 1.1867031482552393e-01
+1.8607083460179050e+00 5.6367680585133484e-01
+4.6316307651096000e+00 9.1304734593243153e-01
+7.2656238526215722e-01 2.3757103871564031e+00
+5.0056910097180776e+00 1.5010449704673698e+00
+4.9624161895295229e+00 2.6960444214353716e+00
+3.3542160631586180e+00 1.1837574387235468e+00
+1.6297305432641829e+00 8.3897199763792007e-01
+4.2567419686803598e-01 6.4977416766460960e-01
+3.9903131288644662e+00 2.3906312912488694e+00
+1.3598719989278285e-01 1.9789845475688617e+00
+3.6205715376752767e-01 9.7198473504529037e-01
+7.8894966559368740e-01 1.6355045947000477e+00
+2.8431040381106798e+00 1.4714640409869020e+00
+4.2279701988811382e+00 6.7939587747631613e-01
+5.2483860548935430e+00 2.5080854514199080e-01
+3.1495306742498217e+00 2.8845730390538518e+00
+2.8281133112077494e-01 1.8730385761465345e+00
+2.0222282794898452e+00 1.5112823240831910e+00
+4.0317042842397708e+00 1.0169050386662306e+00
+4.8300607542620444e+00 3.8144041453300503e-01
+1.8339170405904621e+00 1.1648205024111244e+00
+3.0942921130668100e+00 2.0508442274612260e+00
+5.1272696778617739e+00 4.6698979771536631e-01
+3.6013059393304641e+00 5.9319892455593703e-01
+3.3324435183226919e+00 1.6608577837101721e+00
+2.0660909266588798e+00 1.0913671346721059e+00
+2.6014731567962013e+00 1.4576195550131468e+00
+5.1108277654239949e+00 1.4642373685682553e-01
+2.2245186727274087e+00 1.7753953634360506e+00
+2.9112227289277679e+00 7.6299957658336603e-01
+2.3212329168838837e+00 6.8692791885940252e-01
+4.3903363006021605e+00 1.8078352509988354e+00
+3.4803511004018497e+00 1.0555964177086159e+00
+4.0915151936908707e+00 5.1890337520091601e-01
+3.0913487874426271e+00 6.4472421615185171e-01
+2.4263176884601303e+00 2.5730877421005145e+00
+2.9693071266220770e+00 2.7862750691626337e+00
+3.5869295247412092e-01 1.4082862324915362e+00
+2.5405262322599089e+00 3.0017977857824696e+00
+6.1096789297818352e+00 2.2060076303932528e+00
+4.8312879716589539e+00 2.7968362170590764e+00
+4.5917252192919120e+00 8.7775818520029847e-01
+4.8341512164110441e+00 2.2189377004931998e+00
+4.8118077532076136e+00 4.3389986706087824e-01
+5.2769195031287968e+00 9.9927031902891172e-01
+4.8584763163066711e+00 8.5796296060301236e-01
+6.1639228822123551e+00 1.7135996183554782e+00
+7.1753216276590104e-01 1.9939695672473081e+00
+2.5483437638620754e+00 1.2213570094808521e+00
+3.0749127750740901e+00 7.9169935721384965e-01
+1.6312689936398128e+00 7.3679355945492986e-01
+2.4387203436102523e-01 6.5795398858258458e-01
+6.1326232053553902e+00 8.0827739288419975e-01
+4.6045079202587891e+00 1.5775722812543425e+00
+9.2537051278517968e-01 1.6289104684997291e+00
+9.7907030000537354e-01 2.6429088860495504e+00
+4.4636156070544812e+00 1.6950015212933898e+00
+2.4468715384232858e+00 1.3562262534082807e+00
+5.0037578785442793e-01 2.4333734740862871e+00
+2.5757586794500664e+00 1.8424198184268830e+00
+9.0837831675792957e-01 2.1110765488136480e+00
+5.0818078642838795e+00 1.4356614682902777e+00
+5.8312359615060272e+00 1.6851373377375514e+00
+6.2144130742840300e+00 1.7381599552479166e+00
+4.1667491074780507e+00 2.6625495538765822e+00
+4.3550543042784344e+00 2.0864233852912086e+00
+1.5519837027431178e+00 2.1532776628048813e+00
+5.3094321851901451e+00 1.1379134778534858e+00
+1.8822988409176578e+00 1.1973858791222387e+00
+7.9674224829402895e-02 2.4557358609370477e+00
+5.7467863071275982e-01 2.4310965859518090e+00
+4.9590762584800601e+00 2.5789657356588878e+00
+4.4662759328056243e+00 1.0756759456690488e+00
+4.9450304872795829e+00 1.4639077710404542e+00
+3.8918402055038541e+00 8.1468908900867465e-01
+5.1540779995127348e+00 2.4334786122288987e+00
+2.3480948523923617e-01 2.1023861549546683e+00
+2.4885649418230056e+00 2.6634895054156171e+00
+1.5356044285087800e+00 1.2463166476513501e+00
+2.9884679198061979e+00 2.0147701299164744e+00
+5.5483093179675373e+00 1.1826345899979289e+00
+1.7225893872998137e+00 2.4381803642315987e+00
+3.7707373342925310e+00 1.4725664773572436e+00
+2.7718434140998838e-02 2.7076099036516492e+00
+9.8805103423640528e-01 2.5138622797530457e+00
+5.2409226854113102e+00 1.5835871535758113e+00
+1.2176071038950944e+00 1.1332326625024449e+00
+5.2356401411353710e+00 1.3353877783732451e+00
+2.1668177361956409e+00 2.2305729431079189e+00
+4.2479494530298538e+00 1.3112162044794957e+00
+2.9141380924232863e-01 1.1817699240786839e+00
+4.6645130230189915e+00 1.9668948363878482e+00
+3.1153472887437288e+00 8.8692879506696309e-01
+5.9661656577052931e+00 2.2509746507471586e+00
+1.7632681871529645e+00 2.1484776244831867e+00
+2.3648886769648594e+00 1.3750618224188664e+00
+6.2817162446663399e-01 7.1255650039295515e-01
+6.0511735974666880e+00 8.8777854219843622e-01
+2.6754521702997942e+00 1.4072066412444770e+00
+7.6689689670121997e-01 3.0571535198813748e+00
+4.3964310690593358e+00 4.7076831366838623e-01
+3.1217042935036670e+00 9.3806170236013242e-01
+2.1198961157780221e+00 1.7580966235069944e+00
+4.7948892947127195e+00 7.0645177538456316e-01
+5.2267984684682869e+00 1.1461692682195037e+00
+1.6045370880891994e+00 2.1639963897443213e+00
+3.2984122451278606e+00 2.2891332610976245e+00
+2.7231180294848025e+00 8.0125558687761833e-01
+9.8943631180169511e-01 1.9764124826997314e+00
+3.7184830392305290e+00 1.9012734248655783e+00
+5.8797704586269717e+00 1.7602573754840698e+00
+6.5856810082671746e-01 1.0217588051587676e+00
+5.5957485869955779e+00 2.2908252084991658e+00
+3.4235214052274041e+00 1.1383483302946615e+00
+2.7081803266493383e+00 1.2661112679371309e+00
+9.3636975197132433e-01 8.8562393944549334e-01
+1.5843708705109305e+00 1.8675969753377575e+00
+2.8391053145418610e+00 2.4207691701395260e+00
+3.3107002141764776e+00 7.8570580527667688e-01
+2.2319539958339281e+00 1.1217102506955561e+00
+4.8587327617325577e+00 1.4736536260861601e+00
+5.4753750308283351e+00 1.0933242542084642e+00
+4.5867615644901960e+00 1.0457239059587375e+00
+2.5585192265969630e+00 1.4446990251576519e+00
+3.7816609617230350e+00 9.0449165123940301e-01
+4.0339755962900892e+00 1.2110979528514858e+00
+8.5017469056924844e-01 1.4818055223729707e+00
+3.1106993631180413e+00 1.2060656780898189e+00
+1.8919736095845761e+00 3.8595495252743417e-01
+3.6572865110313377e+00 1.2663838221557326e+00
+5.9342348925468267e+00 8.5191536846286198e-01
+2.7140188662090612e+00 2.2153021182743649e+00
+2.1577208878498277e-01 1.0289208136285239e+00
+5.8278463838887520e+00 2.0929648394225575e+00
+5.7840336951988931e+00 1.7035112720073713e+00
+2.2666363493720754e+00 9.2826403734862994e-01
+1.7004895272169473e+00 1.8205414673327391e+00
+4.8442263588443755e+00 2.7059171261655557e+00
+3.1147140602383621e+00 2.2320508632212732e+00
+3.6362236482204904e+00 2.6992011333898525e+00
+4.3215096761610168e+00 1.2652383759451540e+00
+6.0314062847125571e-01 2.0954390594002210e+00
+5.4917515870377684e+00 1.7271239744819682e+00
+5.9050882413280910e+00 2.2986483857055537e+00
+5.6921471439425764e-01 7.2927219570508495e-01
+5.3375173189223322e+00 1.8969955095714686e+00
+5.6713195585034155e+00 2.2206721286307909e+00
+8.7008129353009611e-02 9.5779870692753732e-01
+3.1853821448107347e+00 2.8427224253350705e+00
+4.1590501783092639e+00 2.7191286870716826e+00
+2.4386514341235968e+00 1.5161600468971781e+00
+3.9925007243529209e+00 4.7576368400338387e-01
+4.7879509362464985e+00 1.3525708320259426e+00
+3.6309410985258399e+00 1.3533139514109278e+00
+3.9500501192982664e+00 1.2602544991684681e+00
+1.7846687572718132e+00 5.8728073665797209e-01
+5.3379999485109746e+00 2.1831968534727708e+00
+2.7157334926381824e+00 6.6802799013971037e-01
+4.0773173643903959e+00 2.6750912791358736e+00
+5.2647427949036860e+00 8.7303934968857522e-01
+1.6835457867306736e+00 2.7309094727546137e+00
+2.5637065278223696e+00 1.7521704137115588e+00
+3.4998511857931729e+00 1.3778965937248031e+00
+2.8178651689507626e+00 2.2051835262561355e+00
+5.6921375280950315e-01 9.6041203211406123e-01
+4.6688443702714419e+00 2.1768395136331051e+00
+1.5750551348630815e-01 2.2295149103397254e+00
+2.7147735366817995e+00 1.3635739800645084e+00
+8.9621368717067273e-02 4.6953208574213434e-01
+6.1128606450800369e+00 1.2614795840791475e+00
+3.2632154325109055e+00 2.2117499318444986e+00
+5.6274078166731165e+00 5.3805153174641474e-01
+2.4671790742958715e+00 1.8237263810765061e+00
+5.5491079067408275e+00 2.7601456822022854e+00
+1.5657622066760664e+00 2.2183566506467987e+00
+5.6864483437972089e+00 6.0321352466358003e-01
+5.5306806603128091e+00 7.4049196128947814e-01
+2.5375665722828598e+00 1.4002445143990609e+00
+3.8962771813278190e+00 1.4261859865950794e+00
+2.5621930715721266e+00 3.0957830129064723e+00
+5.5756161112548366e+00 2.3989074639802088e+00
+1.3102480311438456e+00 2.2079705185522309e+00
+6.2795376959206930e+00 8.4371981573099830e-01
+5.5026709648811289e+00 1.7749329911594238e+00
+1.4237870816608635e+00 1.9534800585236234e+00
+1.4919363850727965e+00 2.1120147596615881e+00
+4.0570241023165430e+00 1.9752488313001129e+00
+1.9164323480325123e+00 1.6932252366468903e+00
+5.2070062944077309e+00 2.2252496966337074e+00
+5.5233244120597966e+00 1.4416228709534105e+00
+5.9268575732760453e+00 1.2490783111435040e+00
+2.4227684381992622e+00 1.2201778403870425e+00
+5.0660863791093602e+00 1.8693639082772555e+00
+1.0059965402355822e+00 3.8251526126519342e-01
+3.9778522256246651e+00 2.2033790110750906e+00
+4.3845257939031006e+00 1.0897109355178947e+00
+5.6534611025981629e-01 1.7826824455192620e+00
+3.3262228216647372e+00 8.3520311816625115e-01
+5.5840474684425008e+00 1.8576952975282557e+00
+1.6053461581471760e+00 1.1313246259415621e+00
+1.4654933795795526e+00 1.9013694837872168e+00
+5.2706555030703717e+00 7.2222767480440908e-01
+3.1146157578721709e+00 1.0241219357524576e+00
+8.9345201983934031e-01 1.6969897158563372e+00
+1.4097222761423518e+00 1.7660192498310958e+00
+4.2298538226739915e+00 1.8545716661493228e+00
+3.5121401278211497e+00 1.6509925309586075e+00
+1.8144888434323097e+00 1.7925302360929247e+00
+3.9200275918864378e+00 2.5713834287028665e+00
+4.5088547670013135e+00 1.6639695280263942e+00
+1.7747494052688559e+00 1.3309950909736872e+00
+2.6108543771542587e+00 1.9406047805651536e+00
+2.3283000543745538e+00 1.7723093021765866e+00
+4.9154121698366744e+00 2.4878822357735402e+00
+8.2323173521899662e-01 1.5783834442089129e+00
+5.7078837492428462e+00 1.9378082696156556e+00
+1.9819857168791672e+00 2.9071763245119895e+00
+3.0932400636499642e+00 7.0136081555293095e-01
+4.9882808516461665e+00 2.1884355897883858e+00
+4.2617043347735377e+00 1.1734750067910036e+00
+1.0264253312167249e+00 2.3141460505474054e+00
+4.4753110022471212e+00 3.1277411138631339e-01
+7.7961843956737598e-01 1.5895533571108456e+00
+5.9196913687794712e+00 6.1297899644516352e-01
+5.5346661454766561e+00 1.1008831189473725e+00
+3.2247938184951694e+00 1.5739615372463494e+00
+4.2721486235821473e+00 1.3552441890774598e+00
+6.1498903748470397e+00 1.8941370531245980e+00
+7.6406117304875210e-01 1.4893693616756549e+00
+4.7879145207268143e+00 9.4411868554331657e-01
+5.2022091532176047e+00 2.0546513577997172e+00
+4.9120787204066545e+00 1.9591986419218952e+00
+1.2022529992895172e+00 1.8363436082318869e+00
+2.6463896489459393e+00 7.2912694742965789e-01
+3.9961111468654220e-02 2.5088792926780781e+00
+2.0542989623490251e+00 2.2057943938058195e+00
+8.1437337100339890e-01 1.9648590795859004e+00
+2.8254927846710411e+00 1.8482470959897632e+00
+3.5820255940357963e+00 1.0888399504507631e+00
+5.0589268590853678e+00 6.2703404976179589e-01
+2.6165780457023393e+00 2.6773199050623027e+00
+3.2611460588972516e+00 2.5167115512048999e+00
+5.8395772092029112e+00 2.8923509998714767e+00
+5.6129261786802669e+00 2.2207560319488926e+00
+3.3502747655591438e+00 2.8268015506607465e+00
+5.6409020103020815e+00 1.5992043205578979e+00
+3.1420699092537019e-01 2.1152802226740492e+00
+5.1844507956938157e+00 5.7953105132393101e-01
+2.1219074804177556e+00 1.1103910488823194e+00
+1.4098910781070628e+00 1.5792222417689497e+00
+5.1286494944385623e+00 2.5349456064191243e+00
+2.2490677740943084e+00 1.5079261686217786e+00
+9.6870854080071833e-01 1.2339543999044613e+00
+1.8658422757623283e-01 1.4145422683258206e+00
+5.9934944605067502e+00 1.6846160650856445e+00
+4.3590750098642594e+00 2.5027217239753812e+00
+5.4295018128210542e+00 9.1071521115903309e-01
+5.9179466514872177e+00 2.1206672312375181e+00
+4.2815615060387069e+00 1.5955692255669784e+00
+5.7873090291433620e+00 9.0754955426546413e-01
+1.6183759208452628e+00 1.5397657919046941e+00
+5.5868653052532533e+00 1.9380252347447482e+00
+5.7487805216748216e+00 7.7591265752795990e-01
+1.8742110974373907e+00 2.6167321236868890e+00
+4.3742945023624902e-01 1.1091835696143664e+00
+4.8849862668359556e+00 6.0732318922926709e-01
+5.4300403892015758e-01 1.6014307106778434e+00
+4.6225796628341591e+00 1.7130956529643164e+00
+2.8079949575323400e+00 2.3166284218226920e+00
+4.0681756646438689e+00 3.6867320906857159e-01
+1.0628041095777387e+00 7.3609425978820597e-01
+3.3175507941486582e+00 1.6195090470355793e+00
+3.9619943168727101e+00 1.6964160157539983e+00
+1.2219980350517634e-02 2.4300623256522478e+00
+2.9829945017956860e+00 5.6875539461896341e-01
+5.3681320266028534e+00 3.0110854135583991e+00
+7.6838814459493499e-01 2.2077802389650585e+00
+2.7687840250103282e+00 1.9387147094276145e+00
+4.2696329782027966e+00 2.6992334176693094e+00
+1.8483665609480320e+00 2.3910388820536670e+00
+6.0084638786527513e+00 1.0884521235427203e+00
+4.3572323814860772e+00 1.6330274671499498e+00
+1.3045742205734385e+00 1.8903806766175513e+00
+3.4412906607647575e+00 2.1427803517875224e+00
+5.5750253672968437e+00 1.6755712057174543e+00
+3.4142233194504490e+00 2.2660257376325186e+00
+4.7059365615639468e+00 1.9340255624053282e+00
+5.5642622278238756e+00 2.0433303292973322e+00
+5.2986387499018717e+00 8.0381471719206765e-01
+8.7712417573104418e-01 1.2001824721768402e+00
+1.1404879873148954e+00 1.9177834363134521e+00
+9.4233414127706494e-01 1.4045932930984233e+00
+1.3403070137319251e-01 2.0285739033539971e+00
+8.3849914227558386e-03 2.0389668653531587e+00
+3.7331302405354299e+00 2.0832755682912061e+00
+3.8176681547882287e+00 1.1448161725122037e+00
+5.7762383249813842e+00 1.5349687872027782e+00
+4.5505984284933705e+00 2.6102617226108027e+00
+1.9035826921617613e+00 2.6587774843537542e+00
+3.0094648710436132e+00 2.1152942225735165e+00
+1.6446639511427674e+00 1.6929798387344324e+00
+4.6261244294035055e+00 2.0872420966338425e+00
+7.3784428697665838e-01 1.2343243077195465e+00
+4.6874625763873352e+00 4.5892880144277348e-01
+5.1327475734152683e+00 1.0451842327601593e+00
+4.6407564557370939e+00 1.3418334107760874e+00
+2.1655823095815658e+00 1.0544640577696016e+00
+3.6112904440470941e+00 1.0552864164839912e+00
+2.9226735900688841e+00 1.3905231583642244e+00
+5.4564773423070800e-01 1.0117223017124624e+00
+5.1510151679870475e+00 1.7891881119801154e+00
+4.3229102197472633e+00 1.3641199061628955e+00
+1.6576112370986833e+00 2.1100535178234652e+00
+6.0585615622634563e+00 1.9824796485974781e+00
+1.1117371383483441e+00 2.8341894813298194e+00
+1.8694928970764284e+00 1.8864293217588666e+00
+2.6127016909131293e+00 2.3621085185432626e+00
+1.4526220451592051e+00 1.7551018229795703e+00
+1.1989066359141636e+00 2.3131244854083004e+00
+4.4090386843953047e+00 8.7610105863649090e-01
+1.1191387512649200e+00 1.0940331462714998e+00
+3.2055043246526416e+00 2.4527184474149442e+00
+1.8040836234431172e+00 8.7559015063464063e-01
+2.9217216764736569e+00 2.1438576204671911e+00
+5.7552065518170314e+00 2.4452506444113853e+00
+1.3602283450584776e+00 1.2496734568517676e+00
+6.1253896342126044e+00 4.2987520065548024e-01
+5.7228654420021288e+00 9.1584913284067404e-01
+4.2947502753249438e+00 1.3014798019740459e+00
+3.1784174144250255e+00 1.9017046273457034e+00
+3.1988274558146470e+00 2.0186390938894698e+00
+6.9346245300324227e-01 1.7466393206767887e+00
+6.2253653871049011e+00 1.8724649241959890e+00
+2.3344244431111676e+00 7.3061238919476801e-01
+4.1365590350589319e+00 3.0054987680874934e+00
+2.5241344338131797e+00 1.1371751257949791e+00
+6.7368379609509255e-02 1.8401068059909882e+00
+3.3530623266904338e+00 1.9415968038220215e+00
+3.1462372553120601e+00 1.2494533674288308e+00
+1.5673474173466539e+00 5.5965892969823994e-01
+3.0244298504050882e-01 6.8399964176428629e-01
+5.3980372987362859e+00 1.1582549031999068e-01
+2.5678918397046901e-01 2.5556980446639632e+00
+2.0764553863544988e+00 2.4040677891250701e+00
+4.7929998175473836e+00 2.6010420712652071e-01
+6.1288241660764546e+00 2.5506912188828195e+00
+3.7145810823130896e+00 1.1800742197387515e+00
+4.9409344456740172e+00 5.4386112468164560e-01
+4.6714561282868612e+00 2.3199941028162616e+00
+4.0807168117956545e+00 5.7654063824978763e-01
+4.3095790131463865e+00 2.8405762914265646e+00
+5.8229271047263040e+00 1.8860823712426662e+00
+3.2725453094880246e+00 2.0921480558346488e+00
+2.4816881962610124e+00 2.2464174160212802e-01
+4.2259891735531010e+00 8.0255090473646162e-01
+2.8460510708406934e+00 9.3524188083324256e-01
+7.2648863344501713e-01 6.4904515627036641e-01
+2.8112770909484057e+00 9.9142303401081477e-01
+3.5114314814895895e+00 7.8032393225069963e-01
+2.5371239686454183e+00 1.8822332299716584e+00
+2.5162696088311760e+00 1.0645453385640893e+00
+4.7422605240576061e+00 1.2583933696180167e+00
+3.1923928055114317e+00 1.9535633646467780e+00
+3.0174996113262136e+00 1.8664781845534260e+00
+5.1831305463111743e-01 5.9788042757481930e-01
+1.6097663029028750e+00 2.7750305405265956e+00
+2.8549503916001364e+00 1.1711947113118693e+00
+4.0121614857440484e+00 2.2441900891746651e+00
+4.4672361345440530e+00 8.6270198454627256e-01
+6.1984389818745687e+00 2.1216561577598840e+00
+5.8323377938364018e+00 2.4871473689538650e+00
+5.6262876719109567e-01 1.3086690905244582e+00
+5.9878732405792796e+00 2.4907209611960619e+00
+9.4694550249378384e-01 9.2867801276138340e-01
+6.1258446790175984e+00 6.2099249220137931e-01
+3.6941176146853705e+00 1.9359591139410710e+00
+1.4832607969951048e+00 2.0622662203632363e+00
+7.2774506980382814e-01 2.9630207948639899e+00
+1.8983960540631239e+00 1.5716212455664311e+00
+4.9862337799554419e+00 2.3600148949601230e-01
+5.1544994536426003e-01 1.0537665791237050e+00
+2.3164456613963997e+00 1.9230031804345651e+00
+8.2433098838823504e-01 1.3963803855395762e+00
+9.4792436172048600e-01 1.0281272023871215e+00
+3.6776242518391999e-01 2.4160265209342340e+00
+4.4325631653775410e+00 1.4149247368846243e+00
+5.5684305456666694e-01 7.9103389642776145e-01
+3.8606445963180445e+00 1.1310131995245798e+00
+1.0946562519969372e+00 2.1553379832477311e+00
+4.1199434661108514e+00 1.8639441090231639e+00
+3.0517742206732112e+00 1.5148831344764320e+00
+1.7450012679088411e+00 2.5620376335071118e+00
+5.2158434649998977e+00 1.7751905509380863e+00
+5.1601169801584819e+00 2.1679415066266472e+00
+5.8659794123731821e+00 1.6353588000871331e+00
+6.2163583753184914e+00 9.2145265251899966e-01
+4.0403273521860745e+00 1.7976809514546130e+00
+1.4226277569536983e-02 1.5614828601970800e+00
+6.8356887973830727e-01 2.5124090551316982e+00
+6.9798084302601815e-01 6.0614839301842449e-01
+2.3268288998468885e+00 2.1566720332614056e+00
+1.4985819039668804e+00 1.8338045389698669e+00
+2.2254190462548324e+00 1.2726507591058589e+00
+1.6082924224704260e+00 1.0858331405325021e+00
+2.4600729060682873e+00 2.2651999478669422e+00
+2.6230824129517343e+00 1.5537714482374674e+00
+4.2255152894907040e+00 1.5769925903589965e-01
+4.9598337858456887e+00 1.1248188661007399e+00
+1.6506493841752246e+00 2.5434446468312477e+00
+5.3120895644365458e+00 2.7335162293138655e+00
+4.6769720283761362e+00 1.6025062881807564e+00
+5.2044603491407901e+00 1.6423985186963748e+00
+1.1356070657414721e+00 1.5796683801658404e+00
+3.4347171708713509e-01 2.2360572153861322e+00
+3.8122542190845632e+00 1.8389334212435204e+00
+4.4383824455269067e+00 6.6316382641340932e-01
+6.3481200978488683e-01 1.0697594271187119e+00
+6.0807397285407161e-01 1.7263964031296761e+00
+3.6964297928736842e+00 5.3998346652999740e-01
+5.0803771371732633e+00 2.4382869172601067e+00
+5.1362080424258627e+00 2.2725963568973508e+00
+5.3925393205270078e-01 4.1638158158052696e-01
+4.6064236494034549e+00 2.4452052595420279e+00
+5.6545315211921974e+00 1.7773947542852955e+00
+2.8675916801854955e+00 1.9595322146717469e+00
+4.8710694159010259e-01 2.6003530423096581e+00
+1.4712918287532502e+00 2.2671778750611895e+00
+4.6167146749291090e+00 1.7915634893699348e+00
+2.4924436506375125e-01 1.9637122017879891e+00
+2.6523622613528262e+00 1.2048733885973886e+00
+3.3345737040354937e+00 2.0985978285510365e+00
+5.9513765699430454e+00 3.7082473449126097e-01
+1.2759047665785634e+00 1.9807128660344833e+00
+7.8961013597777407e-01 2.2821061386621633e+00
+4.0647504160932222e+00 1.4555044834857684e+00
+6.4960764114458103e-01 9.5764396609294056e-01
+6.1377864015788184e+00 1.6560707679033249e+00
+1.5386990457280674e+00 1.2285228138455917e-01
+3.7918782608663650e+00 1.6947146024245474e+00
+5.1089499862975511e+00 1.9471245509684010e+00
+5.2149127308624763e+00 2.1281638412353026e+00
+3.1474420574515092e+00 3.8183672445157124e-01
+4.8163117718172526e+00 2.1524282355115369e+00
+5.6945930497719992e+00 1.8081964765365994e+00
+5.6091119882689302e+00 2.1696090630051819e+00
+1.3340518573887272e+00 2.0968863806985505e+00
+3.4451323480886598e+00 1.7281273217786661e+00
+5.5144068722767781e+00 7.0528590752153342e-02
+1.3801678637006540e+00 5.7490674001903497e-01
+4.6922827609413478e+00 1.4034686082630217e+00
+2.9736509141397303e+00 1.3706641011281444e+00
+3.7040227533630010e+00 2.5674368518852324e+00
+1.6285697806234016e+00 1.4251535737909860e+00
+4.9707907071632151e-01 6.4227410311897581e-01
+1.8548097004718533e+00 1.6614958192549796e+00
+5.7481319027716946e+00 1.7594147009379573e+00
+2.9559362971881988e+00 2.4099321471000250e+00
+1.7203654865466114e+00 1.2619121595249394e+00
+4.8011637592001613e+00 1.6857928719091242e-01
+4.8736098619249226e+00 1.1751703398946196e+00
+1.0758249104634876e-01 1.6903028978336077e+00
+5.4868867428967443e+00 1.2675737261458659e+00
+5.0212189403593541e+00 1.3980558285195106e+00
+2.0416786838534469e+00 1.0413035607960690e+00
+4.2165927860898549e+00 1.2676563386850539e+00
+1.8364454281704210e+00 1.5540038270360443e+00
+5.7354226017059844e+00 4.5839565276628513e-01
+1.8279047055180238e+00 1.3524232065675050e+00
+1.0385542732298632e+00 1.3117088317812604e+00
+4.6989278284414313e+00 1.1200226998172742e+00
+2.9510366322391008e+00 8.0848497286771537e-01
+4.1061703603727242e+00 1.8070377751208291e+00
+6.0346494825845856e+00 1.1342333677804273e+00
+2.0020219539818420e+00 1.5576844280072777e+00
+4.6665840653781920e-01 1.8460338259414300e+00
+5.0058789371595456e+00 1.6919816060499469e+00
+2.3447351036524600e+00 1.2084774763869817e+00
+3.6191985234887838e+00 2.1582058037794636e+00
+1.1341977403626062e+00 2.9309238491203038e-01
+3.2088906694208630e-01 2.9765258464602873e-01
+9.7150957736624699e-01 2.0149328790704750e+00
+5.2069121613918110e+00 1.8672904198414482e+00
+2.0412430381129134e+00 7.9993533344433043e-02
+2.4208727990507204e+00 9.0241148376087643e-01
+4.1245082604666310e+00 9.2227095074192056e-01
+5.1762385440862753e+00 1.2718091914804284e+00
+4.8433714791726992e+00 9.6549815168075981e-01
+2.4148592742121391e+00 1.6645432895520913e+00
+1.1876237094630497e+00 7.9890888355842704e-01
+4.0972627641209742e+00 2.4626929329336487e+00
+6.2468719999883247e+00 1.5372322185301197e+00
+1.7665202111462233e+00 1.0781918013566911e+00
+4.0507125460123596e+00 2.1511325271046307e+00
+3.7112357734599737e+00 1.5262825025839668e+00
+2.0772931322274819e+00 1.8278141214219161e+00
+6.1605289329742972e+00 9.0009316434336006e-01
+7.9039491109877691e-01 1.2456283217826443e+00
+4.5707929467219515e+00 2.7299308638962794e-01
+1.0832886388385479e+00 4.5203907472152238e-01
+2.7332717108456737e+00 1.4041679508293339e+00
+5.2727787997765896e+00 2.5508964547607982e+00
+2.2968692252059637e+00 1.7235685618698389e+00
+5.0179974534438720e-01 2.2083222645340852e+00
+3.4617416952065216e+00 1.0369906305948984e-01
+1.7890804712578008e+00 1.5583360044582051e+00
+8.1361231498602482e-01 6.7017072100120700e-01
+4.4341014475969391e+00 1.2785337638665886e+00
+3.0158676604698749e+00 1.6608119420387184e+00
+1.2168894306565010e+00 2.4762722857618642e+00
+4.5405885382297839e-01 3.8973186775324153e-01
+1.2170771524937425e+00 1.3251575039618773e+00
+9.0265159802200667e-01 2.3000156822922584e+00
+2.0206328206102961e+00 1.8129876364245392e+00
+5.9276849774524791e+00 7.0428886275498359e-01
+3.2887721266848708e+00 1.4770798479660650e+00
+1.5276968963102266e+00 1.4804225359435548e+00
+2.7382585352292934e+00 1.6966394195103223e+00
+4.3325979429948038e+00 2.2796634760185439e+00
+3.4012879561459322e+00 2.0916863812031190e+00
+3.8417335432417437e+00 2.0580660974472473e+00
+5.4347263589930463e+00 1.4905667820514505e+00
+2.3734503598063634e+00 1.4491046261425327e+00
+4.7042388447542498e+00 1.7439219206105299e+00
+9.6126327598872852e-01 5.4450205110515415e-01
+3.1179347113134126e-01 2.0038060847052921e+00
+2.1744347023444019e+00 1.2906706826053442e+00
+5.1168640223532069e+00 1.9983934054715391e+00
+2.4203450672277271e+00 2.0963257202004364e+00
+6.0597832407472509e+00 1.8218541471465168e+00
+3.7664702285326577e+00 8.4685301249259992e-01
+2.1980878010638758e+00 1.4422612507965968e+00
+5.7978262518240840e+00 1.3948390978928136e+00
+5.6210140782783267e+00 2.1215460611253252e+00
+5.2999348289359292e+00 2.4907661084606785e+00
+1.5837130875885312e+00 1.2467150674798000e+00
+1.9325056923528561e+00 2.4486032529943280e-01
+4.8601823619324893e+00 2.4568887986595453e+00
+4.4518233141096601e+00 2.2795596893737535e+00
+3.0463479781510769e+00 2.9874597078138772e+00
+6.0768481927965130e+00 8.2794376776685930e-01
+2.9508469935491632e+00 1.6081162790515313e+00
+6.2218333743715002e+00 1.6134819685831838e+00
+3.1414488380400742e+00 5.9088507566410309e-01
+3.0614292504766270e+00 1.7334546304603664e+00
+4.8320819242879196e+00 1.5227036850767373e+00
+2.4647371549036086e+00 1.1775044512072350e+00
+2.7556201731557386e+00 2.5421256908807477e+00
+8.6802663503307764e-01 1.5330409123671818e+00
+4.7960544276190067e+00 1.5796136661744213e+00
+3.5719074622950435e+00 7.3875030944678577e-01
+4.1042188406031235e+00 1.3235429875498517e+00
+5.0452927306110960e+00 2.4041530516885992e+00
+1.3338967880577097e+00 1.3350352782865025e+00
+5.3806885300323799e+00 2.3206509763729137e+00
+5.7025636806571658e+00 1.7554798624670276e+00
+1.8381457198743707e+00 1.4973417338998156e+00
+4.6535196572486219e+00 6.9250459543491028e-01
+2.1613030277887728e+00 1.2013586474521407e+00
+1.7409496180720032e+00 9.2480650120144636e-01
+1.1238502849522471e+00 2.3562413185222626e+00
+4.1522219016207540e+00 1.5781250486797245e+00
+3.4339052711586353e+00 6.4632237143920612e-01
+5.2200536890829934e+00 2.4689517246674981e+00
+1.6618067142014026e+00 1.3261941663960437e+00
+1.2621961347246562e+00 2.5267974394439734e+00
+2.6984449562522057e+00 1.7779302455837289e+00
+3.0008621966306479e+00 1.1195322841433497e+00
+4.9794823610742185e+00 2.4295375511017574e+00
+8.2506720989567972e-01 9.5620116498507812e-01
+3.4575414153922424e-01 1.3288534158573579e+00
+3.8773704303544623e+00 6.3890419080027994e-01
+2.0573152078495127e-01 1.3538473387527830e+00
+8.4606383248269790e-01 1.6997724120718209e+00
+4.3710974775517224e+00 1.2575592513485019e+00
+3.2038435098150666e+00 1.6180578756312536e+00
+3.3511358198791941e+00 2.5050596052904996e+00
+5.1282782869644388e+00 1.2471508405253036e+00
+5.0418058020141103e+00 4.0741603439043828e-01
+3.1018316005914035e+00 1.6057866415332922e+00
+1.7301294527420878e+00 1.3440549818812926e+00
+7.2726452480962411e-01 1.4636441760553682e+00
+5.4802942651445292e+00 2.1205199699803305e+00
+6.0825121690745467e+00 1.2266556318718023e+00
+5.9614326004566660e+00 2.3107043504072058e+00
+4.0486635095056585e+00 9.6410471574355083e-01
+5.3039795569064818e-01 2.1646601605304383e+00
+4.7132644851294092e-01 2.2888815771844713e+00
+1.0977453075879278e+00 1.1374514125052668e+00
+1.8166409755176094e-01 2.0974843501460025e+00
+4.5497186608044782e+00 2.2733462296394000e+00
+9.4007154467861564e-01 1.3531977075340731e+00
+3.9932474568580432e+00 2.1519962887086965e+00
+4.9577064226176226e+00 2.0404682811880179e+00
+3.5787974840550731e+00 7.8903268255129233e-01
+2.4277731849914819e+00 1.7634308009566713e+00
+5.1499038877894456e+00 2.3849065085558272e+00
+1.8790493326306699e+00 2.2404518676987113e+00
+2.0311413998901018e+00 1.8615363144481041e+00
+5.0068172454711037e+00 6.7031199067781400e-01
+3.1606462727919715e+00 1.3636463967294388e+00
+1.6063484693865897e+00 2.1108581922845366e+00
+4.6249764756239742e+00 1.8467926838702764e+00
+1.0108121920173172e+00 6.5164674875854389e-01
+5.7748960146882968e+00 6.1476378642167606e-01
+1.3596545497866557e+00 1.6597908803802490e+00
+5.7279460390163450e-01 2.0504146876078950e+00
+4.5412375125386495e-01 1.4425574875886060e+00
+4.7972943489804063e+00 2.2624406099172454e+00
+5.1583333081623195e+00 7.0688813936452366e-01
+4.6594598733122794e+00 2.1123781554237500e+00
+4.7716720286961731e+00 2.3248488922416146e+00
+6.0682971082747574e+00 2.7979053555085964e+00
+2.9416359334055646e+00 1.8949768095037853e+00
+4.9737749825240423e+00 1.5255135080589866e+00
+2.6944762658507662e+00 8.7270003081910519e-01
+5.8793212613140060e+00 9.2063216494645961e-01
+3.1712576524944414e-02 2.8902892405324554e+00
+6.2064857403521980e+00 1.8105284418921961e+00
+3.5469586600897083e+00 1.6074026092494929e+00
+2.2597114006834431e+00 1.6059207385194210e+00
+3.4308175280615480e+00 2.7235342139821661e+00
+1.9276998785740327e+00 1.3208131324364227e+00
+9.8793542794918876e-01 1.4045122246828494e+00
+4.3058112814224447e+00 2.0639429688383597e+00
+4.3669387176227819e+00 1.7120192414771722e+00
+5.0176622092763470e+00 2.3505497278217966e+00
+2.2827949438210542e+00 1.9828190657926295e+00
+1.6002936083866151e+00 9.6472827492618141e-01
+2.2015290747207761e+00 2.7365922497446524e+00
+2.0750543102981451e+00 2.0611633924368005e+00
+5.7596026655421024e+00 1.4008532936067741e-01
+4.7462013173461948e+00 1.7118839445046774e+00
+1.7808369628219580e+00 2.3510493859917818e+00
+3.8091253155731102e+00 6.5716875838919131e-01
+4.6916284713284222e+00 2.7675008090657069e+00
+5.2892001807338627e+00 2.4411119649029782e+00
+5.6584461628983602e+00 2.0649702445349418e+00
+3.6877846797133813e+00 9.7156664862801656e-01
+4.3410466518273783e-01 7.0327328869317784e-01
+1.3666268047399297e+00 2.1891707072310149e+00
+5.4557069762566623e+00 1.2236530938790684e+00
+2.6201894886516337e+00 8.5789259792609696e-01
+4.1674075515205793e+00 4.2180978008897063e-01
+4.9045554350501783e+00 6.5860240068179188e-01
+1.5614263562105415e+00 1.8317559988849368e+00
+3.6591207961837111e+00 4.2998210906007950e-01
+1.4412186470881756e+00 1.7035873950530267e+00
+3.5829587832223897e-02 2.3010353777259289e+00
+4.7890661226470606e+00 1.9745197808758306e+00
+1.4345429155488754e-01 1.2054772357460122e+00
+2.4598126130326765e+00 4.9012663488422858e-01
+1.5940682216625981e+00 4.5118140361589076e-01
+1.2472854804279436e+00 1.3725583293879684e+00
+4.2821609521013002e+00 4.2518280951987064e-01
+5.7383157291502647e+00 1.7041533598697158e+00
+6.2004459131557210e+00 2.7324099587518162e+00
+4.6945291691024842e+00 2.0025994226140944e+00
+5.0211328182415196e+00 2.8356505035811108e+00
+5.6871437309336503e+00 7.7330779426229224e-01
+6.0938489590938785e+00 1.6569067774402662e+00
+7.6980927599290450e-01 2.4997136144169607e+00
+3.2440904360837028e+00 2.3604758020889864e+00
+5.1330589438572751e+00 2.1267487341020206e+00
+4.0424761754011511e+00 2.1994915277832132e+00
+5.9507366385632983e+00 7.5848854125400922e-01
+5.9320195028933380e+00 1.0121910513578549e+00
+6.0309350945648728e+00 1.8506890431189915e+00
+4.0965815804007999e-01 1.8524553293291213e+00
+2.7576122615608880e+00 1.3408484007998924e+00
+2.0658237224756189e+00 1.4896753779136440e+00
+7.8822743621823399e-01 1.9200120987666871e+00
+8.6228292503983139e-01 7.3617079877764890e-01
+5.1169786126208363e+00 2.0743533251395752e+00
+3.3740822605446641e+00 4.5616265760261543e-01
+5.9354530446128289e+00 1.7629885136940031e+00
+6.2180969908290180e+00 1.2140632775627738e+00
+2.5569642694425552e+00 2.0275138721763382e+00
+2.3434899989278697e+00 2.7692481005256981e+00
+1.4132370693039549e+00 5.1605836304224395e-01
+2.8001186421359012e+00 1.5201480273303285e+00
+1.6424902339914216e+00 1.1792977181203883e+00
+2.9017861091159087e+00 1.3557130595139431e+00
+2.8287780937051643e+00 1.0544727458990222e+00
+1.7338693750379222e+00 2.3808262851024931e-01
+9.1959895616725396e-01 1.1136428787208670e+00
+2.6719475897170066e+00 7.6806210457325796e-01
+3.5448838805391341e+00 1.9769138222803950e-01
+2.0681218999223945e+00 1.0033505850196875e+00
+4.3697813684054800e+00 2.3850736800610788e+00
+2.8073973330939110e+00 1.8970386018469243e+00
+2.7056101137005175e+00 2.7651113419807567e+00
+5.0622090081250768e+00 1.1635524665778543e+00
+5.1784122484310942e+00 4.3092179101334871e-01
+4.7436629862822608e+00 1.0875464962026209e+00
+4.9579641496026063e+00 1.0835300663224208e+00
+3.0312804218225935e+00 2.1960864491953647e+00
+3.4894044435830773e+00 1.8781613489530227e+00
+4.0241648363648608e+00 1.3965803083153423e+00
+6.1014500948569417e-01 1.6769787362881790e+00
+1.5498248647873956e+00 8.1992747863439763e-01
+3.9049497405134304e+00 8.6817978292405007e-01
+1.9823557219863963e+00 1.9331778420262355e+00
+4.8338280725360647e+00 1.7229157470748733e+00
+1.6945919644090826e+00 1.1688073256828841e+00
+2.3111584799305973e-01 1.4000660409851042e+00
+1.8923950890400305e+00 2.4439955497571666e+00
+3.5726052832525790e+00 2.8944395366263889e+00
+6.0994798834265973e+00 2.4250367365280949e+00
+4.3978295863531436e+00 2.1159458739841277e+00
+4.5659586555762335e+00 1.6723014485714118e+00
+3.4325316904933580e+00 1.5156203462911408e+00
+3.7999656490777474e+00 1.9712227836514051e-01
+5.4623830059009615e+00 1.1452511410319279e+00
+2.4036847158356744e+00 2.7908265229966234e-01
+2.7567250183261143e+00 2.7170648034667106e+00
+1.2945065418702100e+00 1.2930440644285559e+00
+4.7034944998677553e+00 4.0765815280534445e-01
+3.4412054922064756e+00 2.0455309080229309e+00
+2.2647031448726560e+00 4.1518559163231528e-01
+4.3778071948563744e+00 5.8589470996161142e-01
+6.6891044417892065e-01 2.2424507234421283e+00
+5.3695112727022209e-02 1.0575089834490505e+00
+3.7517441394912416e+00 2.3427435135844759e+00
+4.1945817362918190e+00 2.4828015823885266e+00
+3.6034518151779933e+00 1.8750609320620693e+00
+5.7203105581605325e+00 1.8467064102526385e+00
+4.0023274631138257e+00 1.7565799806231457e+00
+3.2635515685833774e+00 1.9590802821380242e+00
+1.6354801868299698e+00 1.9381623293569124e+00
+3.2524187241680025e-01 1.1130444979552205e+00
+4.5928054454154275e+00 4.7493303243280205e-01
+1.0979182496372015e+00 2.5312043763599910e+00
+1.7496378074738592e+00 2.0268938025556515e+00
+1.6461298539781717e+00 1.3829861805991708e+00
+3.4300006065323205e+00 8.6393912397842165e-01
+3.4398875107755340e+00 9.6719016683950021e-01
+4.9881783926436105e+00 1.4523002735586990e+00
+5.4133298137265591e+00 2.7763408372676031e+00
+5.0008114136411548e+00 1.2232657455036910e+00
+5.9997991310674870e+00 7.2966409536566079e-01
+1.2176818954391926e+00 2.5841707318194969e+00
+5.8445582716616524e+00 1.8446843428657498e+00
+2.9270066845905340e-01 2.0693477702755438e+00
+1.4816808563144499e+00 1.6858501441956648e+00
+3.6294554220628772e-02 1.9990777821656771e+00
+4.1736845530488207e+00 3.3232812247768573e-01
+5.6531265887352031e+00 2.3859072562065768e+00
+3.5943944606503724e+00 2.1999455774045944e+00
+9.3439039072945995e-01 6.6153644040025550e-01
+3.0976651720556667e+00 1.3273222394283144e+00
+3.5299025208300039e+00 1.7766171574664509e+00
+1.1337639390901888e+00 1.8339036073812167e+00
+2.1004928183408600e+00 1.2835259584007923e+00
+4.5027787668364159e+00 1.1145093425050596e+00
+3.4408677534587322e+00 1.0218404676026096e+00
+8.6165096273109754e-01 3.5836806353969930e-01
+2.3143073451946892e+00 2.7200164476322790e+00
+2.5198761136852239e+00 1.7322700211961184e+00
+3.5446700502800321e+00 2.9442986014246051e+00
+1.1102732083942080e-01 1.1349835289804970e+00
+3.5809986129742306e+00 9.5164943589156503e-01
+5.8507329048216690e+00 1.4056627125071017e+00
+8.0613013499667341e-01 2.8219711005885517e+00
+4.7186666998278790e+00 2.9742558245181447e+00
+6.1091352289212999e+00 2.6819549480028515e-01
+3.8586910699872217e+00 2.2753144249675517e+00
+4.5606212704282552e+00 8.1241713880105793e-01
+1.9014822601988977e+00 1.8534422944485411e+00
+2.8904288225822219e+00 1.6361492729283642e+00
+2.9217056691245213e-01 2.4605622596320060e+00
+2.4475325663912226e+00 9.6837175898021255e-01
+2.3664261834619627e+00 1.6707172444447200e+00
+1.8188012327456347e+00 1.9914805595566951e+00
+5.1523232190257273e+00 1.8625352120023457e+00
+2.8447705069792457e+00 1.6271272856619090e+00
+5.0909487928490842e+00 1.6921641156861988e+00
+5.0113456150438207e+00 1.1475559578678227e+00
+1.8314531257991904e+00 1.2185258783723762e+00
+3.3029512190350818e-01 2.9735470716944521e+00
+4.2679278640071905e-01 2.5400718276517100e+00
+4.0225189605327305e+00 1.6356605022963036e+00
+2.7053342512631757e+00 9.5431063916612557e-01
+5.6549237401298829e+00 8.9569898735915165e-01
+2.6273363063197399e+00 1.2857953070542589e+00
+9.3878279071639881e-01 1.8074091452381102e+00
+3.4496145376371579e+00 6.9103113934481442e-01
+5.8895791557630570e+00 2.0827984482932971e+00
+4.2616304414531569e+00 2.5755826612563588e+00
+2.4851709791472190e+00 1.1206846314491967e+00
+1.6872618272623379e+00 1.0176333796607435e+00
+5.2755495243412689e+00 5.7470262091530222e-01
+5.9178087892779256e+00 9.6597337832739194e-01
+2.3826593624728738e+00 4.0701335363004532e-01
+4.5998483397998235e-01 1.4861329655333069e+00
+1.1321165295497235e+00 3.4314944647770096e-01
+3.7441240980271111e+00 2.8939092377606990e-01
+1.1376832262362866e+00 1.5189609283949974e+00
+1.9246939312417424e+00 7.7967938112784463e-01
+2.8766157726944614e+00 7.2255077590349615e-01
+2.1962240573325462e+00 4.8880357551439824e-01
+2.0543829671960210e+00 1.6819373816794605e+00
+2.9189848709859900e+00 1.0022139915181385e+00
+1.5124502218446700e+00 4.9990469764596668e-01
+1.9837804557999528e-01 8.9034722833233226e-01
+4.0925749761193311e+00 4.6348352135759807e-01
+1.0059293709590289e+00 1.7067456493620368e+00
+5.0616642313413998e+00 7.8096791563532719e-01
+2.7317631120150456e+00 2.1123672544646088e-01
+2.1192538553767211e+00 1.8524402217810580e+00
+4.6672397560216865e+00 2.2707744528613194e+00
+4.7489987869892740e+00 7.8996974525785313e-01
+1.0572231072183387e+00 1.4028504116965421e+00
+7.2589064802982595e-01 3.3220266060789783e-01
+1.0984951867524462e+00 1.6274439862879477e+00
+3.9402779275196980e+00 2.1549154103064749e+00
+5.3354585469227711e+00 1.2029769677379867e+00
+3.1895263176381108e+00 6.2536784951326796e-01
+1.0233099456945318e+00 1.3663161236675561e+00
+4.5069318734133192e+00 1.9651659269432129e+00
+5.6917345543664588e+00 1.6089722707692440e+00
+1.3631823168500414e+00 1.7684362240273452e+00
+5.4393223159548718e+00 2.5279848430113998e+00
+1.3083053263408222e+00 1.3756698022989742e+00
+5.2231606723945401e+00 6.8896698395449663e-01
+5.4376182735722409e+00 1.4335658914130711e+00
+3.2308522067521941e+00 1.9216356985082486e+00
+2.9653754336376110e+00 7.1871859496568202e-01
+5.4998145145008168e+00 9.1812762557796568e-01
+3.9682422931880179e-01 1.0116127038618756e+00
+3.2033715933795777e+00 1.3422842142329288e+00
+3.8699060849483971e+00 1.4651000971952615e+00
+4.6129691342781705e+00 1.9986686587179734e+00
+1.4411000677087955e+00 1.4350863215763625e+00
+6.1182776815167834e+00 1.6206212452208302e-01
+8.8345091852307878e-01 1.5766953381429969e+00
+4.8269404280428923e+00 1.1824813234781020e+00
+6.0665523531842771e+00 1.6175305730233127e+00
+2.4601028628242707e+00 1.0776384260615508e+00
+6.2655601890580748e+00 2.1949335424625449e+00
+2.1248773918054535e+00 2.0816899768878891e+00
+8.7697658770499043e-01 2.5119466544666111e+00
+2.4298618282997828e+00 1.1492417851228529e+00
+3.4848491074871446e+00 7.3647060534656172e-01
+3.9991163123752473e+00 9.0451250502857672e-01
+3.3885232603871107e+00 1.0495785695042033e+00
+1.9308946591891192e+00 2.4926799830442570e+00
+9.8896280667107117e-01 1.5202679017265126e+00
+9.8247380578083288e-01 8.1204592287880117e-01
+9.1888856171258393e-01 8.3780129309821327e-01
+4.5174064894801367e+00 1.8710455078660870e+00
+2.9247649534460161e+00 2.0437208830151703e+00
+7.4809219139209526e-01 2.4148557989595534e+00
+4.4740806535594659e+00 1.1612001160173504e+00
+1.0678936100958607e+00 1.4553263796625784e+00
+4.9606125307938802e+00 2.9445881909209306e+00
+3.1770227741167250e+00 1.7162699811418147e+00
+3.3674296556383467e+00 1.6872283295306865e+00
+1.3198834602904528e+00 9.7683269144751517e-01
+5.0177386621965931e+00 8.1412850310256513e-01
+3.2699999808191711e+00 1.3793442834927374e+00
+3.5123884346429337e+00 2.3829706749136998e+00
+2.6245311307420098e+00 1.4121701382418845e+00
+4.5722812407864524e+00 1.1206549971834550e+00
+4.3159414280061226e-01 1.6710253817932736e+00
+3.7905241468214315e+00 2.2205390339439157e+00
+5.3604721663599175e+00 2.0399218992570685e+00
+2.6952489934853290e+00 1.1979786022916761e-01
+4.1817563160513327e+00 1.6185060141031058e+00
+1.9278443782493859e+00 8.3930477284904226e-01
+3.7958547933390907e+00 1.3041326939574232e+00
+1.7684246875601706e+00 8.3573161831578546e-01
+4.9206361437053374e+00 4.2947136355238746e-01
+5.0902257172329985e+00 1.3004648139321202e+00
+5.9990159156755585e+00 1.3966214785045710e+00
+2.7659133216219427e+00 1.2891042629517864e+00
+2.8051168547767409e+00 2.6113838574953023e+00
+3.7730206434115683e+00 4.3137948578193264e-01
+5.2098877388751017e+00 6.2699611227340590e-01
+2.0147697054553504e-01 1.1714989080812208e+00
+1.1550296635368074e+00 2.0031350790473210e+00
+5.9242756132107921e+00 1.8595913637833952e+00
+5.9097161525896631e+00 2.5571170212075267e+00
+2.8518455139402983e+00 1.5806627774528867e+00
+5.1458689695261040e+00 1.3555988239345882e+00
+5.7406185292081204e+00 1.0892736225296018e+00
+1.1014856177163797e+00 3.1306391739087509e+00
+2.3246196223599234e+00 2.3494705793234498e+00
+2.5552831102918114e+00 1.6949470454810394e+00
+2.7735343903467600e+00 1.8478918871880778e+00
+4.4012519319986243e+00 4.2239911745210290e-01
+2.9038842749951419e+00 5.4392989626205779e-01
+5.8722658079241841e+00 4.2505940088060834e-01
+2.2381792935988183e+00 2.2318310380119479e+00
+2.2103201434691471e+00 2.3221866024222351e+00
+5.6434694255218245e+00 1.3874162652550088e+00
+3.4047351115007078e+00 1.3412176296985703e+00
+4.7069962873098383e+00 1.1708247958920266e+00
+7.4517296640105712e-01 1.9514740657694936e+00
+2.8368829282068826e+00 1.7373537095513898e+00
+3.9299402310967091e-01 1.9352897237001394e+00
+4.1942813983762548e+00 2.4392876232531506e+00
+4.4336049182870028e+00 2.5300500523081944e+00
+5.7932835635985382e+00 1.4677295923938833e+00
+4.1956428448802043e+00 1.9803518539085521e+00
+4.3334661041629925e+00 2.0247820335581301e-01
+5.3443956089885809e+00 1.0988865788160156e+00
+2.9022933570739244e+00 1.4432529966339931e+00
+2.8833534085084804e+00 1.1020192489893550e+00
+4.9396891162411176e+00 2.9407884951841190e-01
+5.1867062517103193e+00 1.6858614768429272e+00
+1.2107902436885134e+00 5.6673094950628133e-01
+1.2819733704023584e-01 6.7221465869670349e-01
+3.1119020322068325e-01 8.0907428259021352e-01
+9.1353334530240149e-01 2.7241739990643596e+00
+1.0800046727225641e+00 1.8442566805253462e+00
+3.9102372556249199e+00 1.9931469441629042e+00
+1.9017184268540710e-01 1.3012201839806106e+00
+2.9494680448187340e+00 2.2942740588052253e+00
+4.3436692029019399e+00 1.8313692881844819e+00
+6.8253341988131755e-01 7.6586056486647158e-01
+1.4705935522954414e+00 2.1492119191200687e+00
+1.9943048971298412e+00 2.8112821182896433e+00
+9.8064450406181392e-01 3.7673970832006187e-02
+2.0951608217985531e+00 1.3423439108273549e+00
+5.0789781555361184e+00 2.1201719801810595e+00
+1.8667922412890152e+00 1.7038762690470046e+00
+4.8836555903420962e+00 1.0803355286119263e+00
+3.4685080782802706e+00 1.4186311685125659e+00
+3.4514051105147505e+00 1.6530400721681906e+00
+4.5891036326667232e+00 1.3707734575004489e+00
+4.8787317238894996e+00 2.2471248138993971e+00
+5.5770321578247355e+00 9.1879774725398788e-01
+8.2564460352529512e-01 8.9545073964392796e-01
+5.0390462433168848e+00 1.6332497021649766e+00
+1.2116924040930674e+00 1.0361681122717248e+00
+1.3080628719017628e-01 1.7303280776792915e+00
+7.7151299687447983e-01 1.1017958657176385e+00
+8.5281968842362632e-01 2.3266918712430957e+00
+8.2074625531377521e-01 1.5270726081089971e+00
+5.8720066702865417e+00 1.2495032119093137e+00
+1.6582551988936927e+00 2.3382048067982231e+00
+5.8296758145406180e+00 2.6546030859234762e+00
+4.0357957651662346e+00 1.9220823602099695e+00
+5.5243728911459300e+00 1.6627304404405943e+00
+2.3351385653764756e+00 9.9398491774402775e-01
+1.5384156704175287e+00 2.4165403880368155e+00
+1.2420550145733733e+00 6.3915484017454749e-01
+3.3942372311320410e+00 2.1811607754401470e+00
+1.6254652301506947e+00 1.8258654873471103e+00
+1.9198083332839579e+00 1.7410459437468417e+00
+1.3727258857597402e-02 3.2977979284444681e-01
+3.6688600130596649e+00 2.3152471343345153e+00
+6.0164858775859411e+00 6.7655001545430571e-01
+5.3573744215868189e+00 1.4598146120744580e+00
+4.6050267545286960e-02 1.9459564671951484e+00
+3.9760476402862648e+00 6.2506334970267396e-01
+2.2808043964429361e+00 1.4309023070808002e+00
+7.2794487606651037e-01 1.6845764641016794e+00
+3.4144762311115597e+00 9.1379770671219385e-01
+2.6140518188731967e+00 1.8100406455327798e+00
+3.4057587602765378e+00 3.4017762844439092e-01
+5.5272803785841891e+00 1.4912094745968909e+00
+8.8160477696964801e-01 1.0081552453894402e+00
+2.7091983955833658e+00 1.2178295414144853e+00
+4.3320377268011523e-01 2.1877567271692873e+00
+2.3769627259595767e+00 8.6496454322674088e-01
+4.6062101615509405e+00 7.2657908118481485e-01
+6.5729074477846083e-01 2.7000853721546214e+00
+4.1845082995194343e+00 1.3061621383286135e+00
+1.8656459947367328e+00 2.0482401190234278e+00
+3.7310632166580433e+00 1.0731094288160086e+00
+9.2614974272658346e-01 1.5774484464841199e+00
+2.7886516459206772e+00 9.4468748573887995e-01
+4.6026539090097796e-02 1.5226863747565234e+00
+1.3874733165785131e+00 1.3537205133194434e+00
+1.6198699083944688e+00 2.2162686622713146e+00
+3.1081891161314381e+00 1.8195778100261317e+00
+1.3404527811560631e+00 1.9593691569593332e+00
+2.1308176672470465e+00 7.9277587704699115e-01
+4.6968478326075758e+00 3.0124141301850327e-01
+2.5787841891903924e+00 1.3175034020309513e+00
+3.6173185925685702e-01 6.9803049250421745e-01
+2.2674604326034307e+00 2.4256758312601812e-01
+3.0072840734191058e+00 1.2671477352201468e+00
+5.2230142150955574e+00 9.0830323985477812e-01
+1.8812870688755916e+00 1.7632750934702470e+00
+5.1366238155143806e+00 1.6394364392147485e+00
+3.5565837222408407e+00 2.6111040374810779e+00
+2.9173097749616295e-01 8.6511222255740117e-01
+1.6291663280831969e+00 1.8901066253438565e+00
+3.6434629171833901e+00 7.7768384649130440e-01
+2.9499623275408484e+00 1.7442230220893906e-01
+1.2161997029809439e+00 1.6375145382444789e+00
+1.5197595458439184e+00 1.8705136125903352e+00
+4.8346002827928949e+00 2.6583327344355938e+00
+3.8987289772771438e+00 5.9247863651099841e-01
+9.1953481014950267e-01 1.1593240454911522e+00
+4.5239165283881082e+00 1.7097466507373738e+00
+2.6109950659776380e+00 3.3939341754630181e-01
+2.9348955942445949e+00 1.0986469733735300e+00
+4.4810606025603503e+00 1.2537477246519886e+00
+2.5799582716237670e+00 1.5682168257506048e+00
+1.1461038410603397e-01 7.2080040889550323e-01
+5.4929743775389550e-01 2.6315735407023597e+00
+3.7235113508055115e+00 1.6233198707419165e+00
+2.8465826686911027e+00 1.9164663401186350e+00
+5.7940292925071333e+00 2.2096120725641857e+00
+6.2810038299777382e-01 2.2924526227729651e+00
+2.4214711047106849e+00 6.5768836086347870e-01
+3.3740309109283535e+00 2.5458657723795124e+00
+6.3523870080531175e-01 3.6542329449763478e-01
+5.6641655460471148e+00 1.1887998240542144e+00
+4.0739210171348033e+00 2.9142578952199107e+00
+5.9031281169285741e+00 2.9760103572649266e+00
+3.9712572795618910e+00 7.6874713336284883e-01
+9.7028913947819095e-02 1.6293584115085475e+00
+1.9845972032257637e+00 7.5139244914648029e-01
+7.4671509155260019e-01 2.1543240359901601e+00
+3.9704139153409361e+00 8.6502155009004444e-01
+3.9070450537167156e-01 2.0600401867965781e+00
+4.4435733587388153e+00 1.1036407656495584e-01
+2.9749178816640782e+00 5.0873234044905358e-01
+1.9355413667402634e+00 1.4578610424882366e+00
+3.2659972622261906e+00 6.0172217596377786e-01
+4.5106815025024307e+00 1.2989075639937333e+00
+5.1602023131236674e+00 1.0033837442371061e+00
+1.9791056508427534e+00 1.3967840213542877e+00
+1.9414515161802046e+00 2.2574674329636264e+00
+2.1301519515521741e+00 6.3419478620331282e-01
+4.2079232499853507e+00 1.5684032803958274e+00
+6.0064804598633357e+00 1.0515815189559263e-01
+4.7847627593270525e+00 1.8119851140936494e+00
+5.4574056674381604e+00 2.1984682178912007e+00
+5.3891265724374602e+00 1.1272087658517389e+00
+1.0835427664050918e+00 1.9349787667904963e+00
+5.3205744106754933e+00 2.3949295375683164e+00
+6.0461218479589762e+00 6.3175139856787932e-01
+4.8438299639333806e+00 1.1264225576549940e+00
+5.4740744959835643e+00 4.8483654669145437e-01
+4.0062407228958996e-01 1.7636400967768993e+00
+4.0305081879940525e+00 8.4868399358838376e-01
+2.0587519417246072e+00 8.9540733350045387e-01
+4.0701620735684418e+00 1.8386951188353826e+00
+5.4791027577699563e+00 5.8757881719363481e-01
+2.3793140178070420e+00 2.0327016123103370e+00
+4.8356969066465609e+00 2.2954591477235216e+00
+1.0766108181083642e+00 1.6637753819655030e+00
+3.1798981146865732e+00 2.1162916819416084e+00
+3.9244386884594205e+00 3.0434450625325060e+00
+4.4966885392101039e+00 1.3570772698445566e+00
+1.9771249812047085e+00 1.2227007125585077e+00
+1.6147221532340896e+00 1.2167666046176957e+00
+5.7121608986239991e+00 1.1831644508462313e+00
+3.8635079884109320e+00 7.7053197483189484e-01
+6.2555882326361978e-01 2.7506920718486638e+00
+3.9720782382278652e+00 1.1899271556268440e+00
+1.2069263769231491e+00 1.4485153723810380e+00
+4.9232501924077452e+00 1.1688856143211352e+00
+5.3702727105388330e+00 6.7153592005532192e-01
+2.1684213123242340e+00 2.0472566111624575e+00
+8.2564985018231418e-01 2.5810337807067887e+00
+4.8186755345148358e+00 1.9424503719706872e+00
+2.0132901944730395e+00 9.3693837353038445e-01
+1.5727393035125519e+00 1.5984459343062420e+00
+1.1860171073109278e+00 1.6776181550078730e+00
+4.0690536471528560e-01 2.2644953106708132e+00
+3.4925428691162557e+00 2.0188917142039986e+00
+2.5583527047156513e+00 1.1700650178228824e+00
+4.8133310923401282e+00 8.9259256605966986e-01
+3.0348654659142200e+00 1.1571525311910751e+00
+2.4345277284164433e+00 1.4257790814119220e+00
+1.0150100131436490e-01 1.2759924426865266e-01
+1.2885478926105296e+00 1.6296102987479222e+00
+3.3705862138075733e+00 1.5392377514572038e+00
+2.6083836391430766e+00 2.0384795962074320e+00
+5.6408273496114409e+00 1.2303890456049678e+00
+3.4274117166305462e-01 1.7828656319139178e+00
+2.7613955406283162e+00 2.0001218413770747e+00
+3.3369319943040985e+00 1.3177548058630499e+00
+8.0316235343064335e-01 9.9285974032558777e-01
+3.5642979951989071e+00 2.8901855468943705e-01
+5.3772132435937685e+00 9.9963130465080441e-01
+1.1852733058715801e+00 1.3583832482840816e+00
+9.6395714654903664e-01 1.6700696318539834e+00
+3.8351380187129935e-01 2.3021640660480220e+00
+4.1581167428875805e+00 2.2399437408443430e+00
+7.0269657811596442e-02 1.1660266488398991e+00
+1.7361490880682162e+00 4.0801881018573960e-01
+6.1013543288375676e+00 1.3071818314442138e+00
+4.8233702813808446e+00 6.5092770519430998e-01
+1.4720120809321247e+00 2.2134144999025476e+00
+4.3446533362196824e+00 1.2122522914601559e+00
+8.6459085868947483e-01 1.9863449358024781e+00
+4.0383459334874585e+00 2.8332585058775681e+00
+5.3698179222517064e+00 9.0457106036743684e-01
+5.6454983628885840e+00 1.7264141620715501e+00
+2.1951858595010254e+00 2.5009733706721060e+00
+3.1809684266724716e+00 4.9604812351599770e-01
+4.3333549622885403e+00 1.7844273301910560e+00
+4.4516107499782969e+00 1.3760684111406298e+00
+2.8986108554006487e+00 2.0851631492233218e+00
+2.3359514082904882e+00 1.2581249671491042e+00
+1.6628640126987906e+00 1.8541887730506617e+00
+4.7777833026670358e-01 1.5697809976654060e+00
+2.7922976957922976e+00 1.7348390373137670e+00
+1.0498320644526684e+00 1.8969560562201870e+00
+2.5304303758220250e+00 2.0697431024489297e+00
+5.7932279069213131e+00 9.7765289721259752e-01
+3.1264516281122274e+00 2.0943890944349284e+00
+5.8065657231132990e+00 3.9078568071344777e-01
+5.5803303964051665e-01 4.9699121735939977e-01
+6.1902576388616453e+00 2.0297423560319108e+00
+6.3932685355771790e-01 2.1971489128180766e+00
+1.9841365816815968e+00 2.0680493693841093e+00
+4.9088828526813604e+00 2.1273649819365983e+00
+3.7407648049399649e+00 2.1389168942689589e+00
+2.9138792975283643e+00 1.7170062140995788e+00
+1.8512111682267762e+00 8.4198493031370136e-01
+8.7674593367867493e-01 1.3600594922993585e+00
+1.8257853498759142e+00 2.2735945216250637e+00
+1.0162925503682727e+00 8.9775600710133330e-01
+1.9018780511368174e+00 1.9105749620922108e-01
+7.0443772756937983e-01 2.3314281879165879e+00
+5.2518985853726239e+00 7.6857770929464719e-01
+6.1281749364142719e+00 2.0939457128359784e+00
+1.3503338635573874e+00 1.7141901158973583e+00
+4.4428282983633549e+00 1.9592984745643209e+00
+2.3525793445304943e-01 1.6508136413205063e+00
+3.8844686992641684e+00 1.9046701542093389e+00
+4.2551401189063478e+00 2.5141203710980187e+00
+2.2387795760589935e-01 1.2173360006813227e+00
+6.1844912502504510e+00 2.6825350631761626e+00
+9.1928937046757087e-01 2.1905089210368081e+00
+5.4039417194728472e+00 1.5435845831315116e+00
+1.7244306623123136e+00 2.0870940343768121e+00
+3.2900992548835752e+00 1.7061248027992968e+00
+3.2244460686709213e+00 1.3833281367328996e+00
+3.6507307304512948e+00 2.6550278101512825e+00
+2.1505972793940358e+00 6.7503035766271979e-01
+2.6063824418890249e+00 7.9426986613684336e-01
+2.5776804422177824e+00 2.5280295736323284e+00
+6.2009581896383992e+00 2.2548850722274421e+00
+5.1094428398958636e-01 2.9409181103266961e+00
+2.4906323517238373e+00 1.4241560624151033e+00
+5.0308893021933629e+00 1.1040549388735243e+00
+3.6336151404203356e+00 9.5672474542363017e-01
+1.9275522854379523e+00 2.2046911576214252e+00
+5.7171023368700107e+00 3.4670149392182115e-01
+5.9078229304504859e-01 1.5818484113744240e+00
+2.1231596324758222e+00 2.6087514014375510e+00
+5.4727312084114939e+00 2.2653865890930938e+00
+4.1575724185370637e+00 1.9530242739202051e+00
+4.2898732473002772e+00 6.4801401373266154e-01
+2.9904215583734337e+00 2.7367192204167963e+00
+8.5922640272312911e-01 1.8635973099982845e+00
+5.9796004588853968e+00 1.3066552531678886e+00
+5.5064815345597253e+00 8.7364017375421954e-01
+2.5097130827337697e+00 2.7144311329210566e+00
+5.2636720373196262e+00 9.4187112625983993e-01
+2.0515146161406337e+00 2.6765438700931421e+00
+2.3778315930533176e+00 1.9652907221164237e+00
+5.0034526532111663e+00 1.9944681453335051e+00
+6.1085521212855509e-01 6.0445012847337409e-01
+5.9965079463482063e+00 1.4498785155614957e+00
+6.2666970652924672e+00 1.5915872612162081e+00
+1.9028715404457937e+00 1.1060799073998586e+00
+2.9103661776286538e-01 2.4166113189754799e+00
+2.8159542968107738e+00 7.5941474348163018e-01
+4.9793760634918147e+00 1.5929500957894174e+00
+5.9541213433251148e+00 2.9322483492052349e+00
+3.3096967100185202e+00 2.3829478269480191e+00
+5.0731692592598012e+00 2.0358183708989119e+00
+3.5961962735746518e+00 2.7439267346195328e+00
+4.7282115060365832e+00 1.2111832293618090e+00
+5.9711994367555477e-02 2.8470842613569656e+00
+2.9607951264793306e+00 1.5575974664219747e+00
+1.5285894603271348e+00 1.6074943013452978e+00
+2.0194533856416914e+00 2.0347270305859970e+00
+6.0835755981789177e+00 1.4442479371258869e+00
+1.2358289736211256e+00 2.0287450098953275e+00
+3.3504355149831424e+00 5.7119778896486806e-01
+1.1648144675360204e+00 2.9794715517677979e+00
+3.5055327209947005e+00 1.4551088457720176e+00
+6.2506909450306551e+00 1.8126835072676806e+00
+8.2358985575457977e-01 1.8301796249202633e+00
+5.9550767038722032e+00 2.8583986664806922e+00
+7.8536342561999373e-01 1.1945991246218184e+00
+1.1545318020107109e+00 1.4451947521545523e+00
+4.0922776194496997e+00 1.0090895443409340e+00
+6.9890712299956403e-01 9.8840623135368832e-01
+5.3089954782073390e-01 2.0817631992108590e+00
+4.1438851588508427e+00 2.2838017561715613e+00
+1.3313919529916429e-01 1.8335518890424907e+00
+6.1814305079277414e+00 1.1383103295916375e+00
+3.3155462062317307e+00 2.4287782170283747e+00
+2.3890383905412644e+00 1.7216644903551337e+00
+4.5743759436622486e+00 2.8543784905445055e+00
+5.9510833704646604e+00 2.6033005529065822e+00
+2.4165701885282909e+00 1.1030132429811690e+00
+4.3196316514654125e+00 8.7667263210683055e-01
+3.7953186494055462e+00 5.1563570276919246e-01
+4.9854501335102990e+00 5.7889841207151460e-01
+3.7047636680970273e+00 1.2950517814110665e+00
+2.3036588726158702e+00 2.4370446516466817e+00
+5.3764205911181158e+00 2.6847652010636160e+00
+1.4239532271125737e+00 9.5050277068244338e-01
+5.0162674183911165e+00 1.0670553052568661e+00
+6.2662441960398070e+00 1.3588012778982366e+00
+1.8255626353831074e+00 1.0654213142580304e+00
+4.4804404527602877e+00 2.1794061731062131e+00
+4.2019365705660068e+00 8.5240441199730033e-01
+2.7015560847555751e+00 2.9600060904773118e-01
+3.0519877786021694e+00 1.6891279731784843e+00
+5.9683873322985113e+00 2.1038937486822986e+00
+6.0247588542015578e+00 1.6409543277863550e+00
+4.5458413003844260e+00 1.2742083966765732e+00
+2.4304537100517392e+00 7.9160160299204863e-01
+1.4172129987713822e-01 4.2514721301374503e-01
+3.6472865169364805e+00 2.4322292354285437e+00
+3.5065191423472477e+00 1.1438822637534209e+00
+2.2719402824481794e+00 2.5245930881793468e+00
+5.5560274777232461e+00 2.4526335779412181e+00
+3.9535802020680593e+00 2.4318850520111588e+00
+3.9140678513934373e+00 1.6094477966934575e+00
+1.8344847283223706e+00 1.5994275678268193e+00
+4.8222730802459290e-01 1.6555446847484907e+00
+1.8893386647898738e+00 8.8222198614692493e-01
+1.4502803496783776e+00 1.5492831154668165e+00
+2.2177986629975397e+00 1.1737104258674513e+00
+5.3356900569669197e+00 1.5752251373443051e+00
+9.9612375902358075e-01 1.9161657248861046e+00
+9.7204693548596222e-01 2.1136503948403864e+00
+3.3877664167691686e+00 2.9563321767208017e-01
+3.7854047392545973e+00 2.0990370509381235e+00
+9.9857945745730081e-01 9.9201289099243040e-01
+4.0453573421803970e+00 1.5999420762844243e+00
+5.3702633694252508e+00 1.9438903423813028e+00
+4.9629218342432129e+00 1.3520435609678396e+00
+1.4539669056865214e+00 2.9545102376338122e+00
+1.9718073998732892e+00 2.6056961269293555e+00
+1.1523032127352593e+00 5.2993342961242385e-01
+2.8772503431831558e+00 6.4783678535090872e-01
+2.0434643589898203e+00 8.3215883942295776e-01
+1.7579172019472566e+00 6.2854228571515380e-01
+5.7797533861730201e+00 1.2520139902809519e+00
+2.5366318640429264e+00 1.0184933719857605e+00
+2.3888784097735023e+00 1.1866753018987366e+00
+3.7289829175255655e+00 2.2359035723527008e+00
+4.3310305011521333e-01 1.6223159593921450e+00
+1.2686802327604163e+00 9.6218500552033870e-01
+4.8596443423499371e+00 3.0283356276898323e+00
+3.6030539272544795e+00 1.4459075403806987e+00
+1.2267469917667932e+00 1.7048800505005210e+00
+5.2786076270100128e+00 2.6740431653057009e+00
+2.6495898515926499e+00 1.6380007427310703e+00
+3.3821163488729491e+00 8.2298744533272350e-01
+3.2964135829641505e+00 1.2838170009952006e+00
+1.1769917665621232e+00 1.0785441701198781e+00
+3.9921175445398527e-01 4.6557184722924605e-01
+2.8956142170193537e+00 1.8958592767452069e+00
+1.7509559756031923e-01 1.0549960622400234e+00
+2.7500693915743244e+00 7.5135430771869471e-01
+3.5047682405730596e+00 1.7376113795613373e+00
+4.0576768359016606e+00 1.5398836808849798e+00
+2.2386756459338164e-01 1.2741095159405216e+00
+4.4602093724392224e+00 1.6439417494630972e+00
+1.0505024392903071e+00 2.2410253551440062e+00
+3.6954673247758336e+00 2.1964135005364973e+00
+3.8058406258103812e+00 1.7410308995298942e+00
+4.8804244239880834e+00 2.8549044163926256e+00
+3.2475646986010531e-01 1.0122813151904135e+00
+5.4136066813739774e+00 2.6324965390290171e+00
+2.4358675455840495e+00 1.7089237034995060e+00
+2.5465448735690193e+00 1.5165179691673982e+00
+2.1393397638880232e+00 1.3245669570829683e+00
+3.4785416630465749e+00 1.6930434572971860e+00
+1.9148857104696870e+00 2.5424549620099155e+00
+2.3335099720970627e+00 1.9743753897760223e+00
+2.2217295503402914e+00 2.5712374008220458e+00
+3.9698449248169934e+00 1.0669784096289212e+00
+4.2580161218810568e+00 1.8903999928634767e+00
+6.2578784654119559e+00 1.3097951430606762e+00
+5.9266136010323844e+00 1.6076118571481548e+00
+2.3163849838234611e+00 1.8207991040809914e+00
+1.2027993093756426e+00 1.9004695957478870e+00
+5.7593902235338348e+00 2.4999144504346371e+00
+1.8381389950807980e+00 1.8447029757811704e+00
+3.3896248819837780e+00 1.8143251934139117e+00
+5.7455239746186795e+00 1.6141251457558297e+00
+5.1440430944556237e-01 9.4066695610272411e-01
+1.5811083348517947e+00 1.1714956539095669e+00
+2.6622336320370259e+00 8.2578313713502083e-01
+3.6428609087110262e+00 1.5901070933152106e+00
+5.6379109729871395e+00 8.5436270501274292e-01
+1.3339620984505864e+00 1.6151709467870037e+00
+6.0732174298534449e+00 2.0785756662312771e+00
+2.7110792204766412e+00 1.3108990443685846e+00
+4.9286423846161984e+00 1.4109583669027288e+00
+3.2385830967901263e+00 1.4930343575120608e+00
+2.1313231846116256e+00 1.7997642587164933e+00
+2.6915611041360905e+00 1.6702831556437387e+00
+4.5314003919689210e+00 2.4060425028406640e+00
+5.7200626181805854e+00 1.9877844913000686e+00
+5.5679612614415674e+00 2.9704582139986524e+00
+8.6801700532176174e-01 5.1212787708381291e-01
+7.3317274757373241e-01 1.3495447002775507e+00
+5.6548711103812543e+00 6.6462080054356876e-01
+4.1461116590603000e+00 1.7253216685013111e+00
+1.9983491713019408e-01 1.7497609813384698e+00
+3.1016249551108199e+00 1.4677884070113509e+00
+5.5962909810148709e+00 1.3587646558214761e+00
+2.4435123848281193e+00 1.5795215135447294e+00
+1.8249486578891383e+00 1.3993895184563003e+00
+1.4532064888471472e+00 2.4314733383559322e+00
+1.2538059346665504e+00 1.0170213483651624e+00
+2.4300084409625877e+00 1.2849175362451717e+00
+3.7095540791097390e+00 1.8533817942082940e+00
+1.6380423538408171e+00 1.9897010261823811e+00
+3.9087550102312481e+00 2.1039202332523397e+00
+5.0746667336632765e+00 2.3199127054297390e+00
+6.9853034458444041e-01 1.9387760230939695e+00
+4.6224263169920903e+00 8.3250082285725824e-01
+4.1393724715614137e+00 2.1558345694224226e+00
+3.1332419432631484e+00 2.5344226933521354e+00
+5.8829707821340227e+00 1.5932499290426916e+00
+6.3697217915682547e-01 2.4690484765473517e+00
+3.7164790569381800e+00 1.7315525385274193e+00
+1.4935815590895489e+00 2.5073622550252983e+00
+2.0227778347335690e-01 1.1159311000430787e+00
+3.6171320470746084e+00 1.8276626297231284e+00
+5.2871557948005945e+00 1.4673428106860777e+00
+3.4805243109641881e+00 1.2527215965145881e+00
+5.3048030575892424e+00 1.2306098917874830e+00
+1.3381129932155664e+00 1.1451836037426033e+00
+3.9456994463161554e+00 1.9400995092484301e+00
+1.1876511413495472e+00 2.5299656830969171e+00
+1.8775714453554755e+00 1.2934437945491197e+00
+1.2052441170021133e-01 2.0755394862627243e+00
+4.3567099380916838e+00 6.2979661000418197e-01
+5.9185459348298997e+00 1.9920726562150903e+00
+4.7537851244197764e+00 1.1407321050431429e+00
+4.7368880735754262e+00 1.8396859637476422e+00
+3.3231843815917905e+00 1.8486459987915385e+00
+1.7780671276438142e+00 1.3836623083137585e+00
+1.5369380988741375e+00 6.0550619107246040e-01
+1.7950033783726453e+00 2.1132048360515610e+00
+6.0096079739550472e+00 4.1020754834697670e-01
+1.4296362145756887e+00 1.0721773541761330e+00
+2.9450823916642630e+00 3.0296779977884212e+00
+1.3711090022929562e+00 7.7295097186970230e-01
+2.6680176563506794e+00 1.1583593578881677e+00
+9.4090324679472648e-01 1.4548799665566150e+00
+2.3897547409854409e+00 1.8610222567832824e+00
+5.0299003805284830e+00 1.9068355837005502e+00
+5.0890092461994634e+00 2.5795163477711744e+00
+5.6675345653299019e+00 1.6527186022010492e+00
+3.3472035519190562e+00 1.2638135866775171e+00
+5.1657017095232547e+00 1.4132606721544310e+00
+4.4618529522015020e+00 1.7788689800997113e+00
+1.9007718259002004e-01 4.8672772098312111e-01
+8.9372943378562697e-01 1.8227069578919779e+00
+1.4904945425344096e+00 4.4923089317986609e-01
+1.5062684227645795e+00 2.8010699612066920e+00
+1.1018420732047556e+00 1.7045754122957566e+00
+1.4727944707378076e+00 1.1681656974487615e+00
+1.7202170486049571e+00 1.1116357277427076e+00
+5.9738672928369478e+00 1.5309851827102867e+00
+2.2230656998034335e+00 1.3705505100780229e+00
+1.8925116706399552e+00 1.0111925145173166e+00
+2.6037801764004938e+00 9.0021607873013287e-01
+1.9292794531708224e+00 1.9013270991734432e+00
+4.7055566659706924e+00 1.4538500739340892e+00
+2.2627199348550358e+00 7.6231948513321990e-01
+7.8574605589420798e-01 1.4286801933088191e+00
+3.8632820004519575e+00 1.6128078894466300e+00
+2.2485437668862174e+00 8.1602003144717439e-01
+2.2924414756075384e+00 1.2155658096953252e+00
+4.4871970216615331e-01 2.4644690577897390e+00
+5.4440700043233345e+00 1.5563914583342220e+00
+2.8902995170749040e+00 2.8408413859990964e+00
+5.3704231746066622e-01 1.6478852884462449e+00
+5.6099901129970338e+00 6.0980316018668423e-01
+1.6560755724402956e+00 2.6817643764077852e+00
+3.8351541815876029e+00 4.6378190583029233e-01
+4.2131304464723129e+00 2.2930453300757652e+00
+1.2454358225263644e+00 1.8490619577767082e+00
+6.0098106729765268e+00 2.1948181734637826e+00
+4.1884115401892794e+00 2.5451419045743271e+00
+3.3290754661394368e+00 1.7232907293949049e+00
+5.4210634856662896e+00 2.0299523872677860e+00
+3.4414147952213474e+00 2.4819881802168418e+00
+6.8023522313962026e-02 1.5706328197887249e+00
+2.9903602054702780e+00 2.1591398321371789e+00
+5.1100404405674436e+00 1.8458194611249912e+00
+4.6532928897926222e+00 1.4329018195259773e+00
+3.5549626176249518e+00 1.4577622051727954e+00
+7.2666261924141828e-02 1.7610786748711242e+00
+4.5274307982285400e+00 1.8179487725889951e+00
+2.8389824838391338e+00 1.7926875939876896e+00
+4.2328664028360645e+00 2.0694251835746198e+00
+4.7311827384145673e+00 2.2931297114667273e+00
+5.0944830022465020e+00 2.3671307868381875e+00
+2.4215858335698259e-01 2.8888949679576883e+00
+4.9077042334909047e+00 9.8054326984607165e-01
+3.4864051331073176e+00 1.6124463245046334e+00
+4.2872541426668764e-01 1.1588152199027235e+00
+1.6334964211564154e+00 9.9316618147177271e-01
+8.5206488071129582e-01 2.0296041426966780e+00
+3.6146331572026984e+00 2.2645200522225428e+00
+3.3433939629296408e+00 2.5912052796575100e+00
+4.0414182479275240e-01 1.7156081837924280e+00
+6.2458122857480056e+00 1.1753299561161632e+00
+1.6501665838199275e+00 2.8799166685121391e-01
+2.0310516072927727e+00 1.4491677675486219e+00
+1.9278780660090185e+00 2.7441030945354470e+00
+2.6751627356179819e-01 1.4171622636780616e+00
+3.2980170258841945e+00 2.0190909886303445e+00
+1.5724993246832006e+00 1.4985937715254289e+00
+2.6026862528621404e+00 1.7119488503646869e+00
+5.9229619374196929e+00 2.4063366169420624e+00
+5.7523312206768908e+00 1.8053697217097682e+00
+7.4950626264449838e-01 9.9106382934823978e-01
+3.6077714795126701e+00 5.3852924213531495e-01
+2.2718772092330677e+00 1.1627118372573917e+00
+4.4856001433222890e+00 3.7819879539888746e-01
+3.2345239934214263e+00 2.4133835087496940e+00
+1.6218760221515791e+00 1.2904052453149530e+00
+3.0052396927199312e+00 1.8101911141593043e+00
+5.3961276328420951e+00 1.4285536574686011e+00
+4.5807989552702493e+00 2.1373051570733415e+00
+1.2504176614975131e+00 1.7930599561963705e+00
+1.0204133825553301e+00 1.8627366161381578e+00
+2.3727064342853419e+00 1.8189531713660709e+00
+5.3942806122691014e+00 1.7839622896322203e+00
+4.3565818596274433e+00 2.4328591023894908e+00
+3.9778635378208991e+00 1.5769040385638085e+00
+8.7859551837473671e-01 9.1239113776919123e-01
+5.3125940491285384e-01 1.9179766841360828e+00
+5.5088134071835277e+00 1.5559348540687976e+00
+2.6372284903559189e+00 2.4166097531789417e+00
+3.0030173325928162e+00 1.4135435193071488e+00
+2.9089258092053951e+00 1.5910416548423885e+00
+3.1524256443555152e+00 1.8567616348575799e+00
+1.7688055750911620e+00 7.2715782654941408e-01
+1.3932212012712144e+00 1.8714434201006265e+00
+5.6306738516768728e+00 1.6786841192142465e+00
+5.7781935720565567e+00 2.0889679194066537e+00
+3.7801849130578988e+00 9.7831105836187904e-01
+2.3306596490353844e+00 2.1078128311920863e+00
+3.7561916351827920e+00 1.5255134516495141e+00
+4.2543425368910359e+00 1.4861718637062471e+00
+5.6012147364678482e+00 7.0568222804539238e-01
+4.9463580461178891e-01 4.6123731560157211e-01
+3.3523248062785371e+00 2.6815694219096953e+00
+2.5959157842696814e+00 2.6165038658790918e-01
+3.8655215933151559e+00 1.2757156757685777e+00
+3.6043250615391118e+00 2.5695074226562782e+00
+1.3691139234921832e+00 2.2425073271138278e+00
+6.9986637850082056e-01 1.8820305083250535e+00
+6.6520267990247461e-02 1.7218676086915434e+00
+3.6937763633593468e-01 1.0515883769874490e+00
+2.0126579565473230e+00 5.7000881562796968e-01
+4.8306406183561332e+00 1.3249112075201945e+00
+2.1253053007141203e+00 3.4545239224671276e-01
+3.9295613305016168e+00 9.9049088073922176e-01
+6.1974735899725859e+00 1.5129122902885490e+00
+4.0373083065827329e+00 7.4247185821777972e-01
+3.3667884406270812e+00 8.8405270920456958e-01
+1.3906433720174070e+00 2.4406845041370886e+00
+4.6514547696790469e+00 1.0407166838905795e+00
+5.5706564327894625e+00 1.3131177072323816e+00
+5.3844249034884495e+00 1.7388453177185923e+00
+3.7501124140353195e+00 7.9447220920301298e-01
+4.2323306831911420e+00 2.7559782530432959e+00
+5.7381197562902635e+00 2.5445081250893371e+00
+2.7443400716079309e+00 4.8635335612678099e-01
+1.7915036130995192e+00 1.7442250602049882e+00
+3.9724950409108826e+00 1.4561032260462334e+00
+1.8732763516370889e+00 6.1806336111164017e-01
+3.9369593962244540e+00 1.8910030070651558e+00
+3.1477152209371617e-01 1.2416632640471363e+00
+6.2358907455709414e+00 1.1286378813706162e+00
+1.2092806725909224e+00 2.2213553789235165e+00
+3.7915592521104062e+00 1.4247812638068840e+00
+2.2345732151108582e+00 1.6558990194691812e+00
+4.5409926201754827e+00 1.3473365088720564e+00
+1.7256086703369800e-01 1.5027385949413405e+00
+4.4124835728955869e-01 1.5353671171324623e+00
+5.8161629574757416e+00 2.0382743252302502e+00
+5.0516952376183166e-01 7.6082022584821918e-01
+2.0785361603833294e+00 4.8003376490314853e-01
+3.3229561465168849e+00 9.3917281551417053e-01
+1.6379093365792785e+00 3.3862762474165775e-01
+3.3529123632305966e+00 1.4904132094952829e+00
+4.9104095120631568e+00 2.4224277005687851e+00
+3.2718041370387612e+00 1.2472941891408058e+00
+5.8591739229685320e+00 2.2209888683053931e+00
+9.4332908412021910e-01 7.3688192543966680e-01
+2.5905179377196270e+00 4.6842440158004317e-01
+1.7047295113819487e+00 2.5105561293822642e+00
+3.7143218725170652e+00 1.9816835059531017e+00
+2.4174072453924822e-01 8.5017282062437172e-01
+3.8579921780049364e-01 1.6409199618305137e+00
+2.1282428785569971e+00 1.5855372541583541e+00
+5.0453314693112805e-01 2.8461933510798856e+00
+1.0153145534803576e+00 1.4376274247447502e+00
+2.3418314293736344e+00 1.4185714149422084e+00
+2.6512278400097378e-01 1.0474039751554520e+00
+4.4924183856267117e+00 2.5796538310195416e+00
+4.0816574062954833e+00 8.7680658733050687e-01
+1.1179859245540120e+00 2.2455421972634007e+00
+4.0506754425527092e-01 1.5089178169508868e+00
+2.6168382384921647e+00 5.4698707559250348e-01
+3.2399178065552041e+00 2.1533897009380154e-01
+1.2953440297635264e+00 3.2562043615012914e-01
+5.3968243984796702e+00 1.9864994519273522e+00
+3.6723220336819620e+00 1.0247569380407047e+00
+9.9913908766386061e-02 2.2230586173077276e+00
+5.6182413958642421e+00 7.5284338292751840e-01
+2.8238722435929122e+00 1.2628997018124095e+00
+3.7851418340077236e-01 2.4636411561150253e+00
+6.7699139580189449e-01 1.4278142842041746e+00
+6.1766182826594491e+00 3.1715536968182056e-01
+5.3762048812954504e+00 7.9462726197720812e-01
+3.8695889451522643e+00 1.0159191721341552e+00
+1.5423896019703509e-01 1.1576136116644071e+00
+2.0833801393791029e+00 2.1353530490147312e+00
+2.8517509561851457e+00 2.7611448825402629e+00
+6.4808269640912197e-01 1.6468095678546810e+00
+3.5801140899668700e+00 2.4554276101110659e+00
+2.0619936234921470e+00 2.3557142800013429e+00
+2.8335890061950688e+00 1.6822260168062138e+00
+3.6180996166572648e+00 1.2471374528679871e+00
+3.6955391673350046e-01 3.4210208533611719e-01
+3.3475370642899893e-01 2.0419754515372723e+00
+1.8806125635786319e+00 1.5318911323949500e+00
+3.4337264483218819e+00 4.8969534095227329e-01
+3.5541053269457836e+00 2.3400078331048024e+00
+1.5243925179516746e+00 8.8753742927450829e-01
+5.6679436804104686e+00 2.5093884416018515e+00
+5.1529621634926546e+00 6.4827852595589897e-01
+2.7545486141899942e+00 1.5442668614836281e+00
+6.0102516449938932e+00 3.1683457674436433e-01
+3.5642830031711412e+00 1.0115935473043685e+00
+1.4826672364082587e+00 1.1103928226813640e+00
+5.0651167729597981e+00 2.9871997173447093e+00
+2.8666747644591677e+00 2.1718351996100558e+00
+1.5450054796266763e+00 2.1037454948339525e+00
+6.0723375225567962e+00 2.5209205505821841e+00
+9.1347140575501040e-01 1.0587340443373146e+00
+3.1793381995895813e+00 8.8379751186803424e-01
+6.0649102294384267e+00 1.8923392823172014e+00
+4.1292156098534285e+00 2.4171886760944430e+00
+3.8215267860318018e+00 1.2561510412646628e+00
+2.9814074595546414e+00 8.5616448339040385e-01
+3.5466915649896107e+00 1.1922159427927310e+00
+5.7131338473071498e+00 2.3456518388085001e+00
+2.4265340605453320e+00 1.8119563964810068e+00
+1.4421540148515042e+00 1.2342545371649267e+00
+2.6797485861283405e+00 1.5520123786534126e+00
+1.8028488372839551e+00 2.9284542180506157e-01
+2.7429058217784767e+00 2.0600738970555375e+00
+3.9151337008156322e+00 7.4492570333816766e-01
+3.3399357857897027e+00 1.8050546857217695e+00
+2.4426267540027102e+00 2.3873782051788095e+00
+5.5353272229092898e+00 2.5671631466703952e+00
+1.9328065044715679e+00 1.0479078037256833e+00
+2.5564059086818034e+00 1.7979538034168330e+00
+3.1976438977457358e+00 1.7861917978075337e+00
+3.8605933282482008e-01 2.0094226985634052e+00
+1.4424895409503278e+00 2.0134825634378979e+00
+6.9337642271650823e-01 2.0575565311967328e+00
+6.3095188220972842e-01 1.6001016319103907e+00
+5.8551801537899726e+00 1.8577950273496091e-01
+1.3711445881968487e-01 1.7720629256320759e+00
+5.2841608459351104e+00 4.6305216472929045e-01
+3.1835673673141582e+00 2.3009059364901190e+00
+7.2065071258662750e-01 4.8630524844582257e-01
+2.9869705131266056e+00 1.0249498410649338e+00
+3.8177309605330247e+00 7.1530284544860689e-01
+4.9487513076953116e-01 1.3851626580330034e+00
+6.1289850553718015e+00 2.4841291664796641e+00
+4.1652337149229366e+00 1.6644673509910661e+00
+2.9064303127732485e+00 1.2539798102500126e+00
+5.4406674531158714e+00 2.7263507709016288e+00
+1.4899524181469632e+00 2.8791966099200872e+00
+5.1604170597495189e+00 1.9678871408645890e+00
+1.9938902953642066e+00 5.1848195920382834e-01
+5.9314914964591452e+00 1.5552626836854642e+00
+1.5540753019515250e+00 7.0325469843239885e-01
+5.9705194297717128e+00 1.8217373854762569e+00
+3.2653909552479550e+00 7.4453905462307113e-01
+3.4857264459469266e+00 1.5226336894158043e+00
+6.2139327392682340e+00 6.2120800465145321e-01
+4.7964304191377725e+00 2.4881991071522882e+00
+1.9927141870011242e+00 2.2240985685448504e+00
+3.7793961456466230e-01 1.1462676526076312e+00
+2.2375421906741133e-01 1.8726162661835850e+00
+5.3237092745217520e+00 3.0581642648195384e+00
+2.6138184376588187e+00 1.7629407148992644e+00
+2.4500102205833012e+00 1.9384950849940936e+00
+3.2664523347133678e+00 1.7558256568317760e+00
+5.5637678804862087e+00 2.2460885973154392e+00
+2.5920421250395558e+00 2.1923613405254514e+00
+3.7600617068807027e+00 1.3578754974180443e+00
+6.2789378573887156e-01 1.4133403907378375e+00
+2.1449235581856625e+00 2.2969581964398209e+00
+9.6830201001983118e-01 4.9385595000656957e-01
+1.6442594598859679e+00 2.6390275616344896e+00
+2.4128470459306914e+00 2.1990621818871987e+00
+1.7719309812669795e+00 1.6028547912128608e+00
+1.3585046567864494e+00 1.0376414528862621e+00
+4.0533888128969879e+00 2.4093782786246996e+00
+5.0638337590276690e+00 5.5058285513570215e-01
+1.3128524380890376e+00 1.7490413502871636e+00
+5.6900556071589259e+00 2.6615073145451955e+00
+4.6897830959158915e+00 1.2587565296011149e+00
+3.3185912119503707e+00 1.9791818338296623e+00
+6.1183454108083293e+00 9.6821548963436321e-01
+3.7937615676640517e+00 3.8260019290349678e-01
+1.3318567397265775e+00 1.1929428317006443e+00
+5.3548413504771979e+00 1.5333139979173476e+00
+5.4046526263249479e+00 2.4753395375140812e+00
+2.0418599639575250e+00 1.9531438364358829e+00
+2.1090335957030675e+00 1.4908702749484430e+00
+5.2244373128835182e+00 2.9174575958303164e+00
+3.0471260530292001e+00 2.0214967482341155e+00
+3.1345679102481583e-01 1.4307843057474456e+00
+4.9105047130851114e+00 2.2842143828198855e+00
+2.6409758669482186e+00 2.2455791670487795e+00
+1.7871507326672906e+00 1.1812332442232318e+00
+2.1706476395736822e+00 1.4085544557820151e+00
+2.3536341021445191e+00 1.5363445375718199e+00
+4.9509597809626591e+00 3.7148008604639493e-01
+2.4468046487970874e-01 1.3333835584635298e+00
+4.6188679307902110e+00 1.8933020284155266e+00
+5.4786031539205231e+00 1.5232466578286921e+00
+1.8299188853024668e+00 7.5207338771504129e-01
+4.1581718005374544e+00 2.3809599250259716e-01
+1.4994875068528288e+00 7.9930822821249259e-01
+3.9317882628918412e+00 1.8409794857228481e+00
+4.6296975779404059e-01 1.0292730192411383e+00
+6.0734971564617304e+00 5.1803017244264349e-01
+3.7773764727002241e+00 1.2449915305132107e+00
+2.4207460306056179e+00 1.6209656171883451e+00
+1.9759855816627381e-01 2.0064760478734529e+00
+5.3563766232428680e+00 1.3976356769804552e+00
+3.7776941517769509e+00 1.8860930468284363e+00
+5.4210753105554055e+00 3.9756871581737840e-01
+3.1132244471226747e+00 1.6727124257767725e+00
+2.6158366621247264e-02 3.7749066745636961e-01
+5.7315806275240098e+00 1.2247792574576586e+00
+2.6094709161008316e+00 1.1623840763100868e+00
+4.5948054942817116e+00 4.0851912185120232e-01
+9.7446010132136029e-01 4.4450263453313998e-01
+5.1813263133485652e+00 8.0417285718837128e-01
+5.7816448866682748e+00 2.3336263799569146e+00
+4.9406069020886472e+00 8.3769576598568640e-01
+4.7595515267212987e+00 1.6468441435847845e+00
+2.5330431417759884e+00 1.9565755166094476e+00
+4.4824930858253538e+00 1.5032549406598681e+00
+3.5583449392918727e+00 8.5215238920003278e-01
+3.6582030939017285e+00 1.5328077337978157e+00
+2.2295143276153837e+00 2.0607597661112371e+00
+4.5172465366182717e+00 1.9206881223922783e+00
+9.9960609840301462e-01 2.2598525530182085e+00
+6.2732308355248048e+00 1.8553138871195971e+00
+3.5585680349829651e+00 2.2693490721870964e+00
+3.4408625141827942e+00 8.1002353576466191e-01
+3.4208069843315521e+00 1.7859801496852348e+00
+4.4372873972691629e+00 2.9456807568731005e+00
+5.8252192753209906e+00 1.9797472538373728e+00
+5.8879536506131132e+00 7.9142719710217424e-01
+5.6777367388705899e+00 2.0010995382909522e+00
+3.8361781588681589e+00 9.1466361144065089e-01
+5.8168439902279427e+00 1.5178346521844219e+00
+7.7331523363799493e-01 2.3250270039102379e+00
+2.1950355654269655e+00 1.4953287972970386e+00
+2.5760532180247653e-01 2.0074359130796995e+00
+3.3505344003156980e+00 1.0844594366372298e+00
+4.6844987925995660e+00 1.0709099051301050e+00
+1.1431155333618721e+00 1.6809630150936852e+00
+2.7013051869506000e+00 2.2604001683420991e+00
+6.4665719771717711e-01 2.3793018964870072e+00
+1.9247580425464765e+00 2.0644742842810673e+00
+4.8871855292945430e+00 1.3339414231924802e+00
+3.3726389417022369e+00 9.5090484573427103e-01
+4.8407359409242270e+00 1.2348071203312436e+00
+4.1177173830011009e+00 1.9901111654717498e+00
+5.5177949000044819e+00 2.3851426522289501e+00
+3.6141419048565648e-01 2.0974283957465478e+00
+1.0176179551407376e+00 1.1853314915400563e+00
+2.6027515116082531e+00 2.5875549357328156e+00
+4.6767501127362721e+00 1.5535511962189785e+00
+5.4510686253171361e+00 2.4561787382068512e-01
+1.6615754004445416e-01 6.3010036106609735e-01
+8.5572143426790159e-01 1.4280992671624888e+00
+2.7157195209835843e+00 1.0243700485805571e+00
+4.8375422549931901e+00 1.0609628961042530e+00
+3.2927564679534331e+00 1.1072889692154624e+00
+4.7852419644817257e+00 1.2302920523985015e+00
+1.0790371831581380e+00 2.0670872355501220e+00
+4.2697955542161292e+00 1.3979747820525592e+00
+3.8843096487667159e+00 1.3277202638984573e+00
+5.4603261231018330e-02 8.7622999265572998e-01
+4.7313964702989235e+00 2.8303128261106343e+00
+6.2332283754307856e+00 2.1523757946580799e+00
+5.7895443821419112e-01 1.4294138651268673e-01
+2.2691086329547483e+00 8.7791010537895797e-01
+1.1516363580369831e+00 1.3181482604323853e+00
+4.5055684483842242e+00 1.2213529931321268e+00
+1.1488622014654057e+00 9.2166977778104431e-01
+6.1678085339959168e+00 1.9852318987900937e+00
+1.0278334505794007e-01 1.2576187166651120e+00
+3.7249241730871496e-01 2.8291368514078914e+00
+5.0480273481514004e+00 1.5304154936747016e+00
+5.3932017860810504e+00 1.4907675479851843e+00
+3.6258056972423063e+00 7.1448268996872644e-01
+4.6255684083426782e+00 2.2221714413398992e+00
+3.3585428874091323e+00 5.2376877538497402e-01
+3.9068098149326742e+00 3.9281451521374033e-01
+1.1667280626665717e+00 1.8709041771021349e+00
+3.1407138217842077e+00 1.3123098044216817e+00
+2.6396430220502420e-01 1.5156608247951606e+00
+3.1837396927457995e-01 1.5790070621384713e+00
+2.1848809422786353e+00 1.8810435900237099e+00
+3.9819286772565894e+00 1.8662241924475462e+00
+5.6221752259238258e+00 2.5495161188166287e+00
+4.2434925543544448e+00 1.7706956762466062e+00
+4.4364998229569288e+00 2.0807021196594597e+00
+4.4264318328752861e+00 1.5807510555958462e+00
+5.1319904376734069e+00 1.1656997178231814e+00
+4.2139811627913666e+00 2.3902259821074052e+00
+3.2583660972885014e+00 8.5326977519937863e-01
+2.0915909516123738e+00 5.2724655700210166e-01
+5.8842142324262916e+00 8.7749994644520046e-01
+1.5024357761481191e+00 1.5688287672894194e+00
+3.1595402729923978e+00 1.1885785136795670e+00
+4.6794650920414647e+00 1.7108371647475666e+00
+1.5945341826639985e+00 1.6994516617366318e+00
+5.2559305852300202e+00 2.7790128706848813e+00
+3.4569037333884176e+00 5.4457827603513942e-01
+5.6700104632387545e+00 2.7079125625677887e+00
+2.2994671632871424e+00 1.6198681816586520e+00
+5.8741694223888388e+00 1.8101795549594468e+00
+5.8310082715609877e+00 1.4460950105379913e+00
+3.8323817219759437e+00 5.5693800788839898e-02
+6.1021231331969128e-01 6.4719401800799181e-01
+2.2223377515126295e+00 2.9037787553859591e+00
+1.7816971963608030e+00 9.5942102096103621e-01
+1.6538721415333362e+00 1.6522206654933467e+00
+1.4087523954002439e+00 1.8221774536212847e+00
+6.2313220124407671e+00 8.7070907320150526e-01
+5.5232978009991598e+00 6.9467192156107038e-01
+3.0010771166085650e+00 7.8872869413216140e-02
+3.6820807332623118e+00 2.2661077782924046e+00
+1.9889582680998739e+00 7.0592715737394984e-01
+6.2176619849260639e+00 1.9656322818929353e+00
+6.1332436106277939e+00 1.7645704860584270e+00
+5.9810842501537014e+00 8.8757079382627935e-01
+4.2180776986025021e+00 1.8130285748036341e+00
+4.5746311012188965e+00 1.7646833941816404e+00
+2.1576410250923783e+00 2.5438948301226540e+00
+3.2376761384213899e+00 1.2083124494997910e+00
+1.7830093674045375e+00 1.0268508181480316e+00
+4.5496466482180935e+00 2.0114938764288324e+00
+4.9177230914178391e+00 1.8524808167044358e+00
+5.8957229155852664e-01 1.0434934319169802e+00
+1.4033965986678216e+00 1.7209840870039028e-01
+1.2873520977435764e+00 1.2045039846623764e+00
+4.0102723540081371e+00 2.5670669607242234e+00
+4.5870604425835610e+00 1.6364198454280405e+00
+5.2762511143505906e+00 1.3373694897448394e+00
+9.8666005602130458e-02 1.5276118093348987e+00
+1.2674168316684813e+00 2.3942717475233111e+00
+2.9408921120368943e+00 1.9927293526326186e+00
+2.1460670678626950e+00 1.6437058165984975e-01
+2.2082890807769640e+00 9.1652318608275918e-01
+2.8906595684092817e-01 4.7802850300548871e-01
+4.8046323129968709e+00 1.8480634219896990e+00
+2.5171511739542609e+00 2.1255864629850918e+00
+4.2250062889473599e+00 1.2037876253292144e+00
+3.3853135779412189e+00 1.8602687629133643e+00
+2.6666379681539087e-01 1.6029799980739869e+00
+1.9255676079520709e+00 6.8609468294402032e-01
+6.0149616655015183e+00 8.4433007212259159e-01
+5.8277836087176791e+00 1.7810206177011583e+00
+2.3846942800749629e+00 1.0641992771177562e+00
+3.8981751819564670e+00 2.0540510503342686e+00
+3.4724689884855571e+00 1.9571732273126698e+00
+5.2306236316690526e+00 1.9626787677526427e+00
+5.9211021385741924e+00 2.8134493165780046e+00
+4.3852586574452301e-01 5.8580995801431368e-01
+5.5980651716971863e+00 1.1956950879730259e+00
+3.5546209327153910e+00 2.1342604166072263e+00
+1.1857484888163967e+00 1.6068920299783847e+00
+1.4343299949797801e+00 8.8876643748440221e-01
+4.4790074525022030e+00 1.8216759708352959e+00
+3.7580651784616599e+00 2.1791367118393246e+00
+3.6426269493589167e+00 1.1047757075891254e+00
+6.0570968451870151e+00 9.7641759424328589e-01
+3.6101398256391866e+00 1.1433904147524421e+00
+6.2906906485497904e-01 1.3137254147119519e+00
+1.0393308298329327e+00 1.0263438772607423e+00
+6.2585835701681969e-01 1.4925004903101482e+00
+9.2008203046193060e-01 1.9682715075359747e+00
+4.0293055364078842e+00 3.0939154985810582e+00
+4.0623026264129232e+00 1.3545057658134574e+00
+2.3500550953820798e+00 2.2701652924679374e+00
+1.0220763391898755e+00 2.1859119348546345e+00
+2.4835685032475845e+00 2.3450775750489505e+00
+5.0558422104489189e+00 1.5872561349734831e+00
+2.2868258967614077e+00 2.8644642953011283e+00
+4.7208446439613247e+00 1.6115717462588766e+00
+1.7890696578132625e+00 2.6894913747940068e+00
+4.0585446025760143e+00 1.6781782742228950e+00
+5.4901002505779246e+00 2.3321186112819769e+00
+1.3097860139648998e+00 1.4236452572133431e+00
+3.0026175672221052e+00 9.0224268270148189e-01
+7.0099382534208221e-01 2.5539098440776407e+00
+8.1847174261987643e-01 2.1141936484582589e+00
+4.4499810404207496e-01 2.0738541541457098e+00
+5.1302230615053723e-01 1.8758919715427993e+00
+3.9097488500570612e+00 1.7437077475870693e+00
+2.5738466299185783e+00 2.4224481418310613e+00
+4.0687686893307013e+00 1.7622773891381476e+00
+5.3983800654409340e+00 1.2078533827176885e+00
+4.7464917291744140e+00 1.5032461410115787e+00
+6.1080924347662569e+00 1.8290861512789511e+00
+4.8866606923852389e+00 1.2295278711239011e+00
+5.8503199952344609e+00 1.1797420531024669e+00
+4.5884350075837119e+00 2.3880377464964941e+00
+1.5525883901366544e+00 1.6608407417925899e+00
+4.5320244249996628e+00 5.3262886399587761e-01
+4.3836640522218078e+00 1.4096438463495953e+00
+4.8203893262360076e+00 1.4363712965299444e+00
+4.6973518708450674e+00 9.9933643693750296e-01
+2.0467522429117926e+00 2.0949257483905761e+00
+3.2351866360913615e+00 1.0971693800005466e+00
+4.8414227598481414e+00 2.0176731328721997e+00
+3.5790257150681399e+00 1.3549170245080813e+00
+6.0050241803619162e+00 1.3432656250624597e+00
+1.0345755750937373e+00 1.8159396786360433e+00
+5.7063313113315841e+00 2.0494170259209987e+00
+4.7683924732532743e+00 4.8525835968742892e-01
+1.8064046331625321e+00 1.6879145555039683e+00
+3.9282932370916241e+00 1.4713383323404612e+00
+2.8691113378332984e+00 1.2843573534124801e+00
+8.5829064480877104e-01 1.2616188687629046e+00
+3.4142258662213756e+00 1.6831900804101767e+00
+6.0849566852667287e+00 2.2607171533216133e+00
+2.7195160691335301e+00 1.1743446041640166e+00
+3.7160064177237984e+00 4.8587003530117268e-01
+6.9334221033055421e-01 1.8397096255056611e+00
+1.2366619475703788e+00 1.2835353515193868e+00
+1.0644351615373204e+00 6.9146431744237846e-01
+2.1019871094683218e-01 1.6097763650572001e+00
+1.7617819107943866e+00 1.2869399062265638e+00
+1.4088928747638261e+00 1.6608616699976599e+00
+2.3022189090753939e+00 2.4805897814309259e+00
+8.2892319015129301e-01 2.4077792639456290e+00
+6.2720965704825513e+00 2.4792419741629179e+00
+3.1661851252093136e+00 1.6730933476356202e+00
+2.4272110180406554e+00 4.4966842873688107e-01
+1.5270888171332457e+00 1.3007070243249352e+00
+2.3395423437522895e+00 4.9660729506794210e-01
+2.5153634010755637e+00 2.5904842982567078e+00
+5.8148387528361098e+00 1.9323705680857670e+00
+3.1117485732228198e+00 4.5664635935543529e-01
+3.9213193757018519e+00 2.9622054691147093e+00
+3.0512027153442478e-02 2.2431938074540798e+00
+3.6189742584823033e+00 1.0062252080727876e+00
+4.9634175099304896e+00 7.8456863877907335e-01
+4.1615525568769360e+00 1.3823785654797223e+00
+3.3130646894513625e+00 1.5185469165346950e+00
+5.0742535130089683e+00 5.0887778942035733e-01
+6.0810498070170169e+00 1.1088788863771406e+00
+2.0232407229746334e+00 1.1467522580606089e+00
+4.9798138315832805e+00 1.0249042463574272e+00
+1.8456033607028133e+00 6.9895903248036106e-01
+1.5863493877213664e-01 3.7484033698548647e-01
+5.0084820502713745e-01 2.4852952065413181e-01
+2.0824772401488651e+00 5.9546241613507978e-01
+1.9568009527060668e+00 2.0202270406751417e+00
+8.6911063947599398e-02 2.6302638713960587e+00
+9.9597598727267300e-01 8.5734107014925653e-01
+2.7782161516389041e+00 2.2786228538491260e+00
+4.6961194035635643e+00 2.7212155890097707e+00
+4.3156769322519049e+00 9.6524767683397639e-01
+4.5249867545162159e+00 1.5236505177060049e+00
+2.8696781069871897e+00 1.8565679312896508e+00
+5.7641208679393499e+00 1.4218250795911851e+00
+1.2340382515627635e-02 7.4587057765223763e-01
+5.8534899497543336e+00 1.3306767433861706e+00
+2.8794732596406245e+00 1.7525132873044020e+00
+5.6950695730094667e+00 1.4833012091756717e+00
+2.4855647641869321e+00 2.1700652218496579e+00
+3.8354469420231503e+00 2.1862845283584300e+00
+3.4736353288327084e+00 2.2195695091675463e+00
+5.3290838686630817e+00 2.0843069757134125e+00
+1.8070093745365545e+00 4.7417454352213384e-01
+4.1835631098519244e+00 1.2239692519669600e+00
+3.8231899716031030e+00 8.2169493436866992e-01
+4.1253681281275290e+00 1.7660925736868855e+00
+1.9657217050417228e+00 2.3605423422341825e+00
+2.1053807777341107e+00 1.0592264144972479e+00
+1.2674144666797484e+00 7.2014561959447165e-01
+6.4290943072952089e-01 1.8315086676896231e+00
+1.7214763526074792e+00 2.2523067761934721e+00
+2.5180349912732791e+00 4.6270691796962193e-02
+3.4944294651640240e+00 3.7340416818672173e-01
+6.2590401592832769e+00 2.3558719400302066e+00
+8.0649396261929240e-02 5.1551447981493626e-01
+4.4906953193760151e+00 1.7415403802547846e+00
+5.7219565575094915e+00 2.1021887921968716e+00
+5.4455656048777907e+00 2.3632821653835272e+00
+7.7379885654488767e-01 2.0790072687536454e+00
+6.0091844239599892e+00 1.5112866498781865e+00
+2.7070346126051401e+00 2.5058219898588230e+00
+5.4685387203442364e+00 1.3592339894343182e+00
+5.5279642544098273e+00 2.8401416479849635e+00
+2.3155633096064534e+00 1.6651036475068821e+00
+3.9910257232378310e+00 1.9215470180770837e+00
+5.3613338615158845e+00 4.9695321951931093e-01
+8.6543087253296402e-02 1.9954583717330228e+00
+5.0764477394330765e-01 1.6963439662393003e+00
+2.3857335784869362e+00 1.2535728014352077e+00
+8.8954508489642681e-01 1.7494474021571860e+00
+1.8909888443325729e-01 2.3431271487496175e+00
+4.8620668881252058e+00 2.5259360301834333e+00
+2.0822004391006277e+00 1.9299083011496347e+00
+5.1868267767435308e+00 8.5874803838426939e-01
+1.0734899892816054e+00 1.3496747888568681e+00
+4.3647951207823272e+00 2.1644245743399413e+00
+5.4600651067973311e+00 2.9412226038807687e-01
+4.7614098140264467e+00 1.7595961389904233e+00
+1.4332511140747795e+00 8.2685904768714391e-01
+2.3038152941026437e+00 1.5036277670895895e+00
+2.3294470586409357e+00 2.8165924371161428e+00
+1.7446213646385211e+00 2.9576447361713392e+00
+2.1204463667514819e+00 9.6018816304194687e-01
+5.8753415274022780e-01 1.6366319023526630e+00
+2.8290485823288560e+00 2.0040409058918964e+00
+2.9036633562399148e+00 8.4146210911071084e-01
+1.7954708706905231e-01 1.2354966895851216e+00
+4.0926909513632825e+00 2.0291351151396291e+00
+3.9226581221377810e-01 1.2945704281744850e+00
+1.0585147473073189e+00 2.1085919931463910e+00
+4.4361697257012249e+00 2.3419974385369402e+00
+3.4677749177205390e+00 1.7734284151745177e+00
+6.2809362023352939e+00 1.5045337743872891e+00
+1.8044865160877683e+00 5.1871224256294934e-01
+2.3406900668436035e+00 1.0368963076501334e+00
+9.5667506184514117e-01 2.3586852880376270e+00
+5.3316772152539560e-01 3.0137763360847893e+00
+2.8911458312139815e+00 2.3911159416762668e+00
+2.2659052851151018e+00 5.7005899031639329e-01
+5.2542382804777512e+00 1.2062393441212000e+00
+3.2855175981330946e+00 2.4701581065285274e+00
+5.1410658832559957e+00 3.8318765436906910e-01
+2.8646969197397389e+00 2.4628572107352849e+00
+4.7257133218467029e-02 1.6115238819825162e+00
+2.8447067740936305e-02 1.8174732541689076e+00
+1.0269134668611182e+00 1.6232764866726381e+00
+2.2665067569078756e+00 2.6687763552327035e+00
+4.5709694454098431e+00 1.9204208200817825e+00
+1.0277990174662124e+00 2.0307645263678129e+00
+1.7102262070077636e+00 2.1302407270834265e+00
+3.9354589874099539e+00 4.3683441364936182e-01
+1.8714306367865965e+00 2.7892195934945319e+00
+2.0559601844425757e+00 6.6911588675244971e-01
+9.7594721396518969e-01 1.8334695784588542e+00
+2.4644920338265801e+00 1.2394162825325270e+00
+5.5994257301475638e+00 4.5039222433756598e-01
+5.5934405818290269e+00 2.4918363935523775e+00
+2.5052018489967538e+00 1.1961169522855419e+00
+4.2510282870992535e+00 1.7255621046316851e+00
+1.6097172589422784e+00 2.4322701754817362e+00
+6.0216742589995231e+00 1.5850422615800817e+00
+3.8834971731502010e+00 1.6564476302449322e+00
+1.3477237522831571e+00 7.1404817450311020e-01
+6.8977119006064402e-01 1.2428042262453709e+00
+3.5481468236099931e+00 2.0137287083606314e+00
+1.7294726809797123e+00 1.2065507305562775e+00
+4.4778227816954752e-01 1.2868186898383169e+00
+1.0232442571126450e+00 6.0198405452086601e-01
+2.6828336586149248e-01 7.3280197820548298e-01
+4.4024154757457845e+00 1.9231876111945061e+00
+4.9128713080496631e+00 1.6819819163258611e+00
+5.1572857761301290e+00 9.0189366851217700e-01
+1.6488476317807117e+00 5.6650592114187504e-01
+5.5780373982268383e+00 7.8682764624769830e-01
+3.8168333518757347e+00 1.6307810691687379e+00
+9.2287203490058367e-02 1.4327451162949734e+00
+2.1476844906755366e+00 5.5909059413596163e-01
+3.3215625901898949e+00 2.3370159185088402e+00
+3.9530428424833510e+00 1.7980056874604009e+00
+2.8206494751268631e+00 2.0868390607143641e+00
+5.1201043706338112e+00 8.0328635804720006e-01
+8.5915812968623806e-01 2.6273733582404173e+00
+6.1365526348150912e+00 2.0496754956799017e+00
+5.2476657692324649e+00 1.4547522490021199e+00
+2.2079891188799259e+00 1.3272579306428123e+00
+3.8596847614708105e+00 1.7151662807119132e+00
+1.9124964338182768e+00 2.1037980712693769e+00
+5.5509389636678597e-01 1.7390810379882318e+00
+3.2417647757498194e+00 1.8074177288947080e+00
+5.1381277970948398e+00 2.4848863592559480e+00
+4.7931321363233366e+00 2.3693917191367704e+00
+5.9570085985121182e+00 1.3682529127376115e+00
+2.1371593659949570e+00 1.2562046986106217e+00
+2.4975018412632473e+00 8.1403220226863648e-01
+9.4348540023443839e-01 1.5272736259823272e+00
+2.1654029119643954e+00 1.9820826958902382e+00
+3.3915172324680207e+00 2.3574010806835624e+00
+3.5125981220652607e+00 2.6982203175962844e+00
+1.0235976999103782e+00 2.1411960636479437e+00
+7.5067957854272649e-01 1.8302261863127920e+00
+2.5224577549861684e+00 1.4688979369515902e+00
+5.2419587594321158e+00 1.3968708515781010e+00
+1.1471008837024275e+00 1.0380772845163002e+00
+3.1202722168536052e+00 1.3794532303593079e+00
+8.0591277246050108e-01 2.4563950157733476e+00
+5.4323002588002183e+00 1.2665177568568387e+00
+4.7643448480574850e+00 2.5835116138137968e+00
+3.5032234927158283e+00 1.8166272559796683e+00
+2.4661563319052227e+00 1.9843337434304675e+00
+3.0453247014677540e+00 1.0552837372303721e+00
+5.5817065123715830e+00 2.6525202797184715e+00
+5.6298739725719855e+00 9.9505589774739489e-01
+3.0599133587577576e+00 2.1526766956372994e+00
+3.2455208016428383e+00 2.1756073749683260e+00
+5.6753522753290335e+00 2.9308579954981351e+00
+3.5783201133381883e+00 2.4007546864547211e+00
+4.8694390803831435e+00 2.1773309388201794e+00
+4.2080323517381597e+00 2.1576237097592363e+00
+2.9220668372003749e+00 6.8063313045722751e-01
+1.3541863655801778e+00 2.0580132726043043e+00
+3.8415083769083016e+00 2.4621153776266684e+00
+1.1035106464432305e+00 1.0124990704716130e+00
+1.2518288738278878e+00 1.2430159158441980e+00
+5.4348692267532721e+00 1.3153164122131353e+00
+7.8154814282583995e-01 1.8577785848803754e+00
+4.0793965544085795e+00 1.1775320926869588e+00
+2.7708235951734959e+00 2.1075405926801154e+00
+3.8908214896110911e+00 1.9478974809583653e+00
+4.7002663341608883e+00 1.8719291548441459e+00
+1.8976366463581975e+00 2.1705494360440269e+00
+2.6951949545503417e-01 1.2277061814579922e+00
+5.2723081913257230e+00 1.1020640867822553e+00
+2.2224495630802807e+00 2.9902947011292813e+00
+4.1797851529657652e+00 1.8688419563297129e+00
+5.6033830907062860e+00 2.0778698481705740e+00
+1.7289597970948256e+00 5.4994010613570921e-01
+6.2501900974854312e+00 1.0233387097657485e+00
+5.1817239183795785e+00 1.8199097613911228e+00
+5.0440673218170140e+00 1.2069353275141361e+00
+4.1087660215990747e+00 1.1413170321311110e+00
+3.3760229164177495e+00 1.3836331850553312e+00
+2.2429379075998792e+00 3.6772045955905175e-01
+5.2006495979991190e+00 1.0415395372797880e+00
+3.7276966377674512e+00 1.4702301894793131e+00
+4.8559930200742611e+00 2.5693087757868929e+00
+6.1980703788921652e+00 6.6804959872980418e-01
+4.9389206516079671e+00 2.0870059245049228e+00
+5.3812029845193559e+00 1.6513228961781876e+00
+2.9921547006309224e+00 1.9031996118037400e+00
+5.4187472403772752e+00 1.0436930221637928e+00
+2.1570636423226182e+00 9.2490983562088902e-01
+1.3220887891517463e+00 8.7047226531400135e-01
+3.0467121490163476e+00 7.4188447596472151e-01
+2.8871536852602864e-01 2.5084809463270079e+00
+5.2617107348707712e+00 1.2636374002331701e+00
+1.6520437125367802e+00 1.5766378892766704e+00
+4.3486545882392189e+00 1.9255902832744756e+00
+4.9740610810905235e+00 1.4009430460869576e+00
+4.4117950048681696e+00 1.8774100227028978e+00
+4.0654955184580555e+00 2.2766391064412508e+00
+1.4354542227275158e+00 2.0782593500164772e+00
+3.0332372958576679e+00 2.4641047152121511e+00
+5.8469653109600017e+00 2.3388877159060479e+00
+1.4739719582456412e+00 1.9472506392308127e+00
+1.2736690779095829e+00 1.3336955210368568e+00
+1.6138703773390919e+00 2.3837949515361072e+00
+4.8803964622980631e+00 2.6134500038351414e+00
+1.9139216508645669e+00 3.0078237227912128e+00
+3.7482163206614714e+00 9.4339546254513151e-01
+1.1418228638464614e+00 1.2210956796563583e+00
+5.5281843553723986e-01 2.4810538980188097e+00
+1.9526366213880126e+00 1.0979312989728951e+00
+1.4153121143588432e+00 2.2660176098909095e+00
+5.6867463649379060e+00 2.4610550837705256e+00
+5.8962835138024072e+00 1.2053447333881113e+00
+2.3967758129853944e-01 2.2755421797158881e+00
+3.7697130570321313e+00 2.6502419963110846e+00
+2.9004719928593303e-01 2.3669898653885544e+00
+2.5482888652651012e+00 2.9465302092505432e+00
+3.1974261694339208e+00 2.2503897535361701e+00
+5.1922474138349557e+00 2.5744413165934343e+00
+3.9700476420493018e+00 2.7079206557859639e+00
+1.6080686195457308e+00 2.0256727145182847e+00
+5.6651595178810386e+00 1.5581389335167155e+00
+4.8748389876945444e+00 1.7661190650271592e+00
+3.6041959569684816e+00 1.4567163025695251e-01
+5.5917644994894671e+00 1.7195580335301850e+00
+2.2907452938958004e+00 2.3159465669659927e+00
+2.1751375404588043e+00 9.8833238349602626e-01
+3.8697218376505957e+00 1.0841698767136183e+00
+5.9564496482932494e-01 2.5203916248853107e+00
+2.3577428626039474e+00 1.6252304595625118e+00
+1.1386863817944266e+00 2.0765750632835207e+00
+5.0919401127711659e+00 1.7456248955288001e+00
+4.5739653496904369e+00 1.7204300214939647e+00
+4.2162953791569198e+00 7.2161703370037356e-01
+5.7719475898228421e+00 1.1573668604385221e+00
+5.1219080340714020e+00 2.8776933228818780e+00
+3.6119950596876547e+00 2.8464650861780130e+00
+6.9980983206503922e-02 2.1223550905283335e+00
+5.8988691441120933e-01 1.3473889868502944e+00
+3.2517482179985873e+00 5.5763636331008515e-01
+5.9846160979283853e+00 1.8700113596441901e+00
+5.3481413184587145e+00 1.6946576284884081e+00
+5.9046289462327177e+00 2.4505155513388748e+00
+6.1194705042619981e+00 1.1386378114688556e+00
+2.3694215712433717e+00 1.1397905376364315e+00
+2.0085294740858890e+00 9.8902511521835346e-01
+4.8782535434520549e+00 4.8096779955706781e-01
+1.3630666590811227e+00 2.6935736037914011e+00
+6.0360672422436901e+00 9.3140166110417766e-01
+5.8136079972552341e+00 1.6381728702328218e+00
+2.3316477536487974e+00 2.0257179530836806e+00
+2.1648852776112686e+00 2.6937808098654834e+00
+4.3617689080301778e+00 9.1282204860163851e-01
+4.5675358014151985e+00 6.1235983353778278e-01
+5.0048978044754255e+00 9.4902198388455961e-01
+3.6955190834247640e+00 8.1244187025904535e-01
+6.5303969780860660e-01 1.9113101858074286e+00
+3.4829231821344155e+00 2.2646005926273869e+00
+2.3745215131284025e-01 1.8280466759586500e+00
+4.7453795392595728e+00 2.0416026055805703e+00
+4.1479119167703837e+00 2.8636058915010483e+00
+3.6917930986343093e+00 2.0446364712068839e+00
+1.4188795974581281e+00 2.1496401772948888e+00
+4.5385669679022795e+00 2.1796569941266655e+00
+4.5659935246246963e+00 1.8703917960542682e+00
+2.2455247253332766e+00 1.4674149314836411e+00
+1.5301791445156088e-01 2.4087950766842159e+00
+5.3242342801640188e+00 2.2353508550884991e+00
+4.8499062056399218e+00 1.5669803361867447e+00
+3.1486735649651578e+00 1.7495919336600882e+00
+4.4382279585297253e+00 1.1131409831997505e+00
+4.9897198047724594e+00 1.9513111228302811e+00
+2.1108885512245044e+00 8.9390509358980541e-01
+5.7098182426601616e+00 5.6013890542414035e-01
+3.5284367758831281e+00 9.6617221784698848e-01
+1.8616600849845879e+00 1.9320112566763907e+00
+1.6749358424417420e+00 1.0825734959311708e+00
+5.5884335537083007e+00 2.8031298843108776e+00
+2.3373796979597596e+00 1.3387220187919635e+00
+9.5599120517033276e-01 2.4731494147194377e+00
+5.9667835324547820e+00 2.7221673892005080e+00
+4.2126237384409286e+00 2.6135486121604008e+00
+6.2077753555857225e+00 2.8174308624174018e+00
+1.3233474980228512e+00 4.0387480535152798e-01
+5.2104995708782997e+00 1.5639823702347213e+00
+3.0445005765260928e+00 1.0089409907709881e+00
+3.6832740132171344e+00 1.3798637367304663e+00
+3.1305140533471665e-01 1.0637940744498071e+00
+3.4475908731298177e+00 1.9184460106458270e+00
+4.0235070326594293e+00 8.0131179234073224e-01
+3.0407193492029068e+00 1.6070614677494055e+00
+3.6131354754389733e-01 2.3662850470637298e+00
+3.1821410690267649e+00 9.3209999078891337e-01
+3.5725102829620070e-01 1.2146730102804613e+00
+5.3430208774892254e+00 1.7953650462875954e+00
+3.7139411610974999e+00 1.1192259423040101e+00
+4.3725728571646920e+00 1.7558922829134780e+00
+4.1180852667156183e+00 2.6135398094774569e+00
+3.8461006752070839e+00 1.3821355360434266e+00
+5.9718166524999541e+00 2.0010725396585429e+00
+1.0846562058166551e+00 1.7950432825636717e+00
+2.9553189457489593e+00 1.4267365748424414e+00
+5.3188083475949934e+00 1.9463633893257852e+00
+3.0907732473624159e+00 2.3408129636050785e+00
+4.7442682865069603e+00 2.1667421849733648e-01
+1.1339316489135527e+00 2.4842479748623956e+00
+4.4625774421311561e+00 1.9175712827305298e+00
+5.6892048838352292e+00 2.7549562996138537e+00
+1.0673316150841794e+00 5.0242650514288356e-01
+2.6627474228939039e+00 1.8077319818711579e+00
+4.1351014595676316e-01 1.9808369220940181e-01
+1.4041016273388032e+00 1.4749476187271824e+00
+4.2574598985635017e+00 1.4405101089186598e+00
+5.4182311502748934e+00 2.2385313997277074e+00
+2.2111323263955653e+00 2.1882789906353453e+00
+1.5965134672673864e-01 1.6083512607119710e+00
+5.6688816296454885e+00 2.1704717036230363e+00
+2.8469020961780052e+00 5.0476649330361156e-01
+5.7551078087330882e+00 2.0428461758579872e+00
+2.1197626927415283e+00 4.0085966453847854e-01
+4.2850539149273894e+00 8.2596522914211234e-01
+1.1900116384470971e-01 1.8768313633994915e+00
+3.3976677247853626e+00 2.8679198301173292e+00
+2.1334369433847975e+00 1.8967427711786637e+00
+2.1702752811871635e+00 1.8341627304587995e+00
+5.9910812006391350e+00 2.5407296714638874e+00
+4.7074481747190360e+00 3.5501532616405695e-01
+2.1190859794816346e+00 2.2441884059910198e+00
+4.0652357736229554e+00 2.3612966193808265e+00
+2.6016490551175568e+00 1.6103402377385598e+00
+3.6147616530481974e+00 1.6306345600478540e+00
+6.0739976067300212e+00 2.6580932605200980e+00
+3.8592819110386398e+00 1.8132038609641568e+00
+8.9593427445318063e-01 1.3990034511224614e+00
+5.4507785489224201e+00 1.6763319122866482e-01
+4.6420586577148848e+00 1.7545887705350809e+00
+6.6539499271139402e-01 1.1092934712382989e+00
+1.0549611651281694e+00 2.8901061766350669e+00
+1.8384110683506416e+00 2.3268607228075826e+00
+2.6297824381129979e+00 6.8386949760855420e-01
+3.2379744118093563e+00 2.6785863469940465e+00
+1.7372927323370768e+00 2.7854410768317046e+00
+5.2068269960159217e+00 1.4464965516207344e+00
+4.9905802618049488e+00 1.8805520048692028e+00
+1.7062427161747771e+00 2.1876464727729541e+00
+3.7996765358047351e+00 6.0287078676702943e-01
+6.4360807103702333e-01 1.1620760980444524e+00
+4.2901557539066708e+00 1.8438534190960774e+00
+2.2784053131743480e+00 1.7754569528368029e+00
+4.2468452772223753e+00 2.2026301861303006e+00
+1.8203684854382147e+00 2.1700987285026527e+00
+4.2484921527000665e+00 6.0500227946141005e-01
+2.5153842992393058e+00 5.6227572506400914e-01
+2.5012536142245030e+00 9.8238317003313314e-01
+1.2678996676353564e+00 2.1188548367523543e+00
+4.5600111912442554e+00 1.4723211107495682e+00
+5.1497983066857085e+00 2.2235584286209980e+00
+5.8476497481575063e+00 2.3916969447998371e+00
+3.2090478888890308e+00 1.1673768564817957e+00
+1.2753653427401976e+00 5.8873717856689012e-01
+3.7644103987252717e+00 1.0387986145346058e+00
+1.3186095909948208e+00 8.1614140525463863e-01
+2.5525996987159369e+00 2.1611325736480009e+00
+5.3352414353071982e+00 1.7443360338838665e+00
+2.7153219879957793e+00 1.7326466476134628e+00
+5.6929548365062566e+00 4.9618020617601299e-01
+2.3809821468695596e+00 2.4835001005200437e+00
+4.4179814622816060e+00 1.4661810815717167e+00
+3.6633006777384276e+00 1.3196281800340037e+00
+6.6669565862067603e-01 2.0065020883809366e+00
+2.6511695913602953e+00 2.1966533373773043e+00
+5.7960429191821330e+00 1.7505182191771413e+00
+3.4577017373859587e-01 1.8769576903368290e+00
+2.3783464404664314e+00 2.3052332004339133e+00
+1.5326171182341608e+00 6.5563721232862937e-01
+1.3945291730694807e+00 1.7136088862496446e+00
+2.2408410091685980e+00 1.2156475855722300e+00
+5.7672848140572022e+00 2.1397747948021690e+00
+3.1455225306196208e+00 1.6294507958581788e+00
+5.0896989051259149e+00 2.7068910008428797e+00
+2.2463002321552352e+00 1.5596935764794309e+00
+2.8552582835788916e+00 2.0509223704553956e+00
+5.9783832144147784e+00 1.1365402548936887e+00
+8.7018298666647276e-01 1.0870955480186815e+00
+4.5349342745467780e-01 1.8064670883196285e+00
+4.4667853966869266e+00 1.4585231004743704e+00
+5.7925341464618665e+00 1.1104640476465029e+00
+6.7678319066908221e-01 1.3402825374472289e+00
+4.1093840672451538e+00 1.3816974343764181e+00
+9.2939064233914648e-01 2.5921357182217397e+00
+4.4251764058913219e+00 1.8290843584760130e+00
+4.0889268977608886e+00 2.7853037615433887e+00
+2.6557646790991418e-01 1.7428015880446013e+00
+5.0762106762461121e+00 1.2504440267666366e+00
+6.1600188422167097e+00 1.4797963646408236e+00
+2.9120312751422168e+00 2.9075606867751125e-01
+2.2104383125723253e+00 1.7060038948436957e+00
+2.8234125154513379e-01 9.1305282110760100e-01
+1.4235028242169856e+00 1.3867212066130634e+00
+3.8937788549891854e+00 2.8289404496828991e+00
+3.7846369022747171e+00 1.5829072301150544e+00
+1.6146020479225198e+00 1.7428732914218648e+00
+4.1790931720898232e+00 2.1992708498851457e+00
+3.8631074764249229e-01 2.1491724082361916e+00
+5.8863343693499530e+00 1.6742002155095774e+00
+4.9205244510481050e-01 5.3610421593374702e-01
+2.1461010526156321e+00 1.9397842045029252e+00
+4.2274712088153930e+00 2.0265250687316292e+00
+3.0558506448716383e+00 1.8409625113158381e+00
+3.2810271822752335e+00 1.5776973886030572e+00
+2.3323255311884545e+00 8.9396368002518167e-01
+4.8562744583356006e+00 2.1199003626453976e+00
+1.7182210098326076e+00 2.8337798718434368e+00
+4.1616775829636481e+00 2.0201349037139518e+00
+5.7757023297337611e+00 1.9988434985301249e+00
+2.1559880085954841e+00 8.5762152512808210e-01
+5.1436615801102752e+00 2.3162109921963800e+00
+4.9941674850653186e+00 8.6329290697001781e-01
+7.1172521502496000e-01 7.2894967085320239e-01
+9.7779523723925221e-01 1.4737994415571167e+00
+2.4846532877025695e+00 2.8623912651966004e+00
+4.3426271775088203e+00 1.5498000338621314e+00
+4.1380570506940559e+00 1.0997589984651421e+00
+4.2865659150549416e+00 1.0680478465586527e+00
+6.1401312304266931e-02 6.4390361886973357e-01
+8.2262245482204932e-01 1.1545127294867812e+00
+4.8362658185294185e+00 5.2797680979510897e-01
+5.3095434332895657e+00 2.9527915555038855e+00
+4.7230798644942880e+00 8.4244660879688726e-01
+5.9168641022923154e+00 2.7675215581886827e+00
+5.7768386277706902e+00 1.5792781656676604e+00
+1.3806582925566848e+00 1.2059223167342370e+00
+3.1829791792511144e+00 1.0240004171904287e+00
+4.9435079631605898e+00 1.9906545945811898e+00
+3.2423038886444826e+00 1.3133865832125668e+00
+3.9414443369441674e+00 6.7050443371643187e-01
+3.5277189159517142e+00 1.2636780697559566e+00
+4.5771234530842246e+00 1.0033307158006983e+00
+3.7602157181562079e-01 1.5772905642266071e+00
+3.0173682108174460e+00 1.7577581346996694e+00
+1.4730135155201556e+00 6.9759005945682451e-01
+2.1739166444679476e+00 1.1501823908273523e+00
+3.5061122262760489e+00 8.2720738525335624e-01
+4.7300846490001529e+00 2.8872923165198245e+00
+2.5539324063800244e+00 2.4683902856855835e+00
+3.1531366463422721e+00 1.4922133223366032e+00
+7.9734878655301156e-01 7.4862256664774152e-01
+1.3521948290290511e+00 2.1417191957197046e+00
+1.8432889913364341e+00 9.6483423265273727e-01
+2.8100343744850385e+00 1.3121836842882237e+00
+1.5229786008173269e+00 1.1472651575768651e+00
+1.6196265455926357e+00 6.8166162346899228e-01
+5.5167720046094457e+00 9.5711592855610150e-01
+3.6435830088453551e+00 2.2207019388415210e+00
+5.8229775041913072e+00 2.7390905202745612e+00
+3.8451261492897673e+00 2.3554256031706795e+00
+1.5496080292578001e+00 1.1102650008660917e+00
+6.5813745159510972e-01 2.4214534021336718e+00
+3.4078986462004832e+00 2.2189685165943933e+00
+2.7993911908007130e+00 1.6358099513540394e+00
+4.3284332073666087e+00 1.6655361645719271e+00
+4.6921421612673067e+00 2.5484434339078277e+00
+2.5330601979623784e+00 1.6115529524043710e+00
+3.5629300451456598e+00 2.5336151801745004e+00
+1.2384462963248501e+00 2.2665723880198922e+00
+5.9430717192220381e+00 1.1057356979290269e+00
+3.1339767595900221e+00 1.9170874430282834e+00
+1.7526559110924036e+00 1.9875577340105073e+00
+1.4846487713828829e+00 8.5444219415979239e-01
+5.9084155141868067e+00 2.8217362583226069e-01
+4.3581854409897192e+00 3.7247309014862062e-01
+4.4920610456926386e+00 1.4164881956351669e+00
+4.2096979287418108e+00 2.1120516315579163e+00
+4.8612404363665211e+00 1.9791168634916363e+00
+3.6067809311914187e+00 1.1937997605940982e+00
+3.1463525405105184e+00 1.4390059564944069e+00
+5.5070731119240381e+00 2.2231932536901837e+00
+2.4775628579211988e+00 3.7522440830542303e-01
+1.5257122961668059e+00 1.9144478891454895e+00
+1.7148920754091086e+00 2.3607370505351377e+00
+5.7035498772026099e+00 1.8874051093308510e+00
+4.4055341967015194e+00 2.4638270499245696e+00
+5.3268280238290089e+00 1.0167165500735940e+00
+2.3853826470142718e+00 9.3619382722246036e-01
+3.9759167247738683e+00 1.3830597870448185e+00
+1.8634002984392906e-01 2.5333420316545125e+00
+5.7390762696019832e+00 2.2131770137210589e+00
+2.9730214715714740e+00 9.7905702315682297e-01
+3.2328042174679816e+00 1.8724814691986911e+00
+4.8544547872346584e+00 7.5729243451509709e-01
+2.1579515747302143e+00 2.3814236764560981e+00
+3.9710730009240987e+00 1.1385872552106155e+00
+2.0548630319945289e+00 2.2544683788029012e+00
+1.9498139213019250e+00 1.7833796870415322e+00
+4.1321868443193983e+00 1.9093197103712360e+00
+2.7915141814552324e+00 5.4588833764888678e-01
+6.0148241197027863e-01 8.3023787726289622e-01
+1.4558653852985328e+00 1.3507674314591418e+00
+6.0681201090520709e+00 7.6429336815811988e-01
+4.4560958537639852e+00 2.1305665210841762e+00
+4.1229404942799439e+00 2.5669673495888192e+00
+2.0605658298099820e+00 1.2479490980490502e+00
+7.7516848380819070e-02 1.4800059558753340e+00
+3.5543128826232784e+00 1.5481111490547266e+00
+2.4553326198466685e-01 2.0526733677426723e+00
+1.6709820093294157e+00 7.9145703074927087e-01
+1.2045647033084765e+00 8.5333958515031194e-01
+2.7026325389989783e+00 2.8651825518362517e+00
+4.8697731990997939e+00 1.6574377403449989e+00
+3.9013310888180617e+00 5.0293696948137456e-01
+2.0488823794431692e+00 1.6330494554964772e+00
+1.9007910499533005e+00 7.3621658345104946e-01
+5.1793215066473941e-01 1.4816647815727388e+00
+1.1089274095816632e+00 1.7601761706764822e+00
+4.6014482254325575e+00 2.8000674131664294e+00
+5.6639522948553056e-01 1.5275841828782390e+00
+5.7889514999523648e+00 6.7677539987190216e-01
+2.6257897322052575e+00 6.4062566867924320e-01
+4.8741105452071976e-01 2.0458246170949250e+00
+3.4498400142182128e+00 2.4208308157428764e+00
+5.2692329220192304e+00 3.1002757814664772e+00
+2.2703704136584353e+00 1.9303304959947765e+00
+4.9260152850688854e+00 1.5024954004146920e+00
+4.4187787567228529e+00 8.1655839637310368e-01
+3.6895039716955425e+00 1.2274381625851261e+00
+2.4179717345667644e+00 5.8718055211815234e-01
+2.4689053828080940e+00 1.0274458662999431e+00
+2.7725299647073696e+00 2.2338336830535788e+00
+2.7261134028970293e+00 3.7632466741931170e-01
+1.4001070102039719e+00 1.9147108650458600e+00
+3.0271031100408563e+00 1.3628356005060211e+00
+2.3734894655023231e-01 1.0886658252347097e+00
+3.5317879988585359e+00 6.9546233665979751e-01
+2.7165145385099621e-01 1.9138217070715271e+00
+5.4706511987380333e+00 1.5985503008703399e+00
+5.4313231096113830e+00 2.3012598300487221e+00
+2.1739989940378210e+00 1.5895207600021761e+00
+6.1273434730222300e+00 1.6044103205775035e+00
+6.0843215243217426e+00 2.3734199352195233e+00
+6.2137254017081052e+00 1.3622563733434585e+00
+9.4307651973781725e-01 6.0170854703098364e-01
+6.2479167989153668e+00 1.6551938816350478e+00
+1.5406587477166713e+00 2.3030465034938308e+00
+2.2057566941770252e+00 2.0081250147632725e+00
+1.4653740401878885e+00 2.6870724601217257e+00
+4.6508782819236849e+00 2.6443571391300313e+00
+1.3644524665879429e+00 2.8028440559992371e+00
+2.7850159429793870e+00 1.4783567556336434e+00
+1.6024006357053444e+00 1.4668952137691218e+00
+2.2012464578665805e+00 1.9269985815057327e+00
+5.5924399254308819e+00 1.0484048351567192e+00
+5.6002315221398646e-02 1.2427755189194469e+00
+7.7328101628237489e-01 4.4152341121150651e-01
+2.4851243403359811e+00 1.6115778897812922e+00
+5.2860871412622297e+00 1.5275854165818448e+00
+1.3814613084850431e+00 8.8944974139126609e-01
+5.9096655168657506e+00 1.4204914021352155e+00
+6.1103239474537903e+00 1.0193416004297187e+00
+2.3410055423330611e+00 5.4098219981357554e-01
+6.1612974577719966e+00 2.1802798547953159e+00
+7.6744515968062643e-01 1.3796732372688914e+00
+4.8265418161017113e+00 1.6714715696198166e+00
+3.6093779192107869e+00 1.3036495795116541e+00
+3.9345932494970750e+00 2.2384947413282630e+00
+3.0747495811598133e+00 1.1837271180721427e+00
+1.3461448177344550e+00 2.8439534974901273e+00
+8.7839516408391294e-01 2.1432632978899946e+00
+3.9079270291116392e+00 5.4826758187032598e-01
+5.0574325746086419e+00 9.6041512456946077e-01
+4.3045580949414530e+00 1.4936567795592233e+00
+2.9974912372394424e+00 2.5529772035249865e+00
+5.8754113357830073e+00 2.1789773323850308e+00
+3.7713253678837244e+00 2.9284563434775643e+00
+1.4962058843662815e+00 3.1753486571623579e-01
+4.3291448237047945e+00 7.9508799048574574e-01
+2.0413670758678877e+00 1.7455452231392354e+00
+4.5335669840403297e+00 2.2212654457984815e-01
+6.1757701842796866e+00 9.5621990879669361e-01
+1.6877082930053464e+00 1.6213155471572729e+00
+5.9099588232485472e+00 2.3556582923350491e+00
+4.4192574169365590e+00 2.1722380099869039e+00
+5.3668333439899563e-01 8.4931427975350926e-01
+1.5257586040106674e+00 1.9645637755078587e+00
+5.8439458050530266e+00 1.0808262839841567e+00
+3.0126829983893795e+00 2.2760938403091813e+00
+5.5149679603551132e+00 1.1440560519131293e+00
+4.7283932763897498e+00 2.6793552347538521e+00
+9.3838588722498617e-02 1.0868826878274178e+00
+4.8358834078444417e+00 2.4018030856474142e+00
+4.2380259757370968e+00 2.8350887594996599e-01
+5.4798093639143097e+00 1.4141867561601287e+00
+3.5957277240836576e+00 2.0509593960373405e+00
+3.9093268854715260e+00 1.6948902738780347e+00
+6.3255396493403815e-01 4.6294240958775856e-01
+1.2886464361779451e+00 1.5706668000794548e+00
+1.6351743091325410e-01 2.6570849390210762e+00
+2.7698335518802857e+00 1.2010387700712521e+00
+2.1493262636430708e+00 1.6409714475808086e+00
+1.7753979437084664e+00 2.3996955891243923e+00
+3.0796670699771620e+00 9.7758348991147237e-01
+1.0920762815545446e+00 1.5096248495312705e+00
+5.7913144388376976e+00 2.5830910142745949e+00
+5.8930368155362070e+00 5.6372854836668584e-01
+3.7415948795826486e+00 6.7763624750879836e-01
+6.1940333517136903e+00 2.6370470019883259e+00
+5.6604638134348146e+00 1.2862985171836643e+00
+4.2960406136815612e+00 5.6886118310609723e-01
+4.6106933907294456e-01 1.8954513302815885e+00
+6.2677072985322102e+00 1.2324541458131135e+00
+2.0020762823693667e+00 1.6470529323573500e+00
+5.0819963784788866e+00 1.6443115998328524e+00
+2.9232161841286461e+00 2.9246561140463294e+00
+3.6906130073413164e+00 7.4889252754074809e-01
+3.0762411862550283e+00 5.0423206756188055e-01
+3.3900067182477160e+00 1.6351643845389436e+00
+1.1017581920813639e+00 1.4071162316325267e+00
+3.9279345111887638e+00 1.4875107452083647e-01
+9.9525823772118294e-01 1.7840053705573164e+00
+2.8632678871124124e+00 2.1220730801683603e+00
+3.3621124652588588e+00 7.6232910662853104e-01
+2.9012088964603531e+00 1.1541292538070023e+00
+5.2036059395590115e+00 2.2757902180035448e+00
+6.2474095237297087e+00 2.9481739334996613e+00
+6.0805026885454989e+00 2.3106431746259846e+00
+4.6908681377704271e+00 2.2240746046225461e+00
+1.3394561588433549e+00 6.1872142651492412e-01
+2.4813502509794061e-01 1.1588812450659443e+00
+1.1730738458974628e+00 2.3896221914840781e+00
+3.6221326412425690e+00 1.7292285521424113e+00
+3.9304316723535990e+00 1.2053188637735368e+00
+2.3605066272933297e+00 1.9070296208670752e+00
+4.0154893177669537e+00 1.3439747076662001e+00
+5.5582569880166481e+00 2.1353116608515434e+00
+1.5276415523437863e+00 2.2526055639918985e+00
+1.7605267621517291e+00 2.1997744446696195e+00
+7.6708843220746470e-01 1.1487285491635975e+00
+3.2074229030756305e+00 2.0740420655632610e+00
+5.1448782367709347e+00 2.0397015981393003e+00
+1.8803403287404272e+00 1.6143559089036494e+00
+5.0134339637152783e+00 2.1337410868698243e+00
+2.6995354896118635e+00 1.8871780751104312e+00
+3.4238522329711496e+00 1.2682316516414587e+00
+6.1918387669652262e+00 1.0517654423517284e+00
+4.3288507764742583e+00 1.5936833278863709e+00
+6.1056332593611966e+00 1.3539226497514276e+00
+5.7513638791749511e-01 1.9830383759682373e+00
+5.6606506575953608e+00 1.9331782754852096e+00
+4.1431187857431562e-01 2.3428396277102159e+00
+7.6127507423282026e-01 7.8390609487838558e-01
+3.9467174693049945e+00 2.0408728352594698e+00
+2.2538383657255530e+00 6.3095796626316225e-01
+2.1212692621151019e+00 2.4771804121379910e+00
+5.0520153970478923e+00 1.3273158780247503e+00
+1.9006254015958781e+00 2.0115721593329861e+00
+3.8016506639863694e+00 1.5332625094113275e+00
+2.8867933368059773e+00 1.4902632856369360e+00
+1.7677937543223623e+00 2.6066569043020547e+00
+5.2259867412566718e+00 1.7127091710780353e+00
+3.5362827384289721e+00 2.1765514412229359e+00
+3.7244327411076084e-01 5.7598564982316824e-01
+1.6612093150026532e+00 1.1284233737978626e+00
+1.0906416029089154e+00 2.4074197014218357e+00
+3.5358734384721027e+00 1.8548384018459254e+00
+1.2142198110066482e+00 1.9892953947520566e+00
+3.0736789221355258e+00 1.9813405652212150e+00
+4.6341809210673146e+00 1.3873045181102777e+00
+5.9865930585851252e+00 9.5658289581436795e-01
+4.8459969985346270e+00 1.6117241356740124e+00
+4.5407481140463535e-01 9.3284554260410446e-01
+5.9056602851632647e+00 1.7175221066543804e+00
+6.1224936027676016e+00 1.0655836541381354e+00
+3.7573624686006548e+00 1.8444590178609268e+00
+5.8433543667669383e+00 4.6808811065847844e-01
+2.1256553180159514e+00 1.0143410970991018e+00
+5.6604130564598538e+00 2.8721730652079396e+00
+5.2651003472695024e-02 1.0116745866661854e+00
+3.0647999220333091e+00 5.5098047648149651e-01
+8.9753994505505208e-01 1.9107313877516743e+00
+1.9949540951073392e+00 2.5632553933541034e+00
+1.1183805895331900e+00 7.7045215097828834e-01
+3.7645355189409848e+00 2.4803815351255052e+00
+4.1491304830316009e+00 1.4263349098712461e+00
+2.1953356959368238e+00 6.0195137277857524e-01
+3.3499930094071351e-01 1.5005532118568496e+00
+1.4688715893045146e-01 2.7021073194621916e+00
+2.9758658333972932e-01 1.6351422875435291e+00
+4.0428137637642392e-01 1.2019856029790770e+00
+4.2641896704818549e-02 2.0824298175815876e+00
+6.1141991119941377e+00 6.6996151088941192e-01
+4.6284101879768018e+00 1.6696228292194799e+00
+4.7703783762641052e+00 2.1301388475325598e+00
+2.1038594406346078e-01 2.2194092191208976e+00
+6.6442008509994643e-01 8.1923409060930030e-01
+1.6357524354596209e+00 1.0459553101608376e+00
+5.5844736584563464e+00 1.2420648737795952e+00
+6.0562406330810532e+00 2.4643119610555617e+00
+6.2085886265967698e+00 8.0496692608732145e-01
+2.2102594519233993e+00 1.6124968472615513e+00
+1.8720652700437257e+00 1.2508901456860533e+00
+1.1735851236855470e+00 6.2316984940847764e-01
+4.6649626521142782e+00 2.4298111201544872e+00
+4.5333180765385386e+00 1.4032095845142574e+00
+1.2332971257778951e+00 2.8761754665669375e+00
+2.7774902731642630e+00 2.4769063015884738e+00
+2.9651032997076987e-01 6.1708144587324631e-01
+4.1843922502348958e+00 1.4722762877133406e+00
+4.9122884828722562e+00 1.2776522681218192e+00
+5.3935290883629090e+00 2.0791878564008783e+00
+4.9433332671909165e+00 1.3130549800682241e+00
+2.6526868997756843e+00 2.5467968957025495e+00
+2.2599779519501682e+00 1.3123889334091818e+00
+3.8530859392931927e-01 2.2099129165528222e+00
+5.1855922737839579e+00 1.3308299056050814e+00
+5.2393204868238863e-01 3.4461903036172914e-01
+6.7942064786508105e-03 1.2739715438652117e+00
+1.6834171524938006e+00 1.9134459947006131e+00
+5.9321348062689845e+00 1.6537223943350110e+00
+4.4436853944144534e+00 2.8255802867368498e+00
+2.4050593396238877e+00 1.5434172154843333e+00
+5.6143862824949125e+00 2.0301244477149050e-01
+4.2373993630381426e+00 1.1141090863536198e+00
+3.8062845855374228e+00 1.7918063425143267e+00
+2.7808267165405853e+00 1.4080076093392790e+00
+6.0382261890475242e+00 1.4682433392083944e+00
+5.1550669143098231e+00 1.5680203731643318e+00
+6.9183893152144460e-02 2.3369507239299030e+00
+9.0198381251621695e-01 2.0627052136350170e+00
+3.0581937266239883e+00 1.5623167838675893e+00
+3.6239931314221518e+00 2.3723870134914820e+00
+1.8360388769511808e+00 1.7385705195273455e+00
+2.7978098258737223e+00 1.3651100098933229e+00
+2.5452438315546728e+00 8.7041631397320407e-01
+8.5250729723634899e-01 1.6139140719782143e+00
+6.0312275242025590e+00 1.2164637911979606e+00
+2.2662134888972512e+00 1.0264265551544793e+00
+5.2668072628171361e+00 2.2606893532960961e+00
+2.7998637793025027e-01 1.3598111487123523e+00
+2.6575239974671154e+00 9.8212984228998335e-01
+4.5741898364303424e-01 1.7121801957944052e+00
+2.0137462878949046e+00 1.7030683763597523e+00
+5.9769618596678651e+00 1.5825472171676089e+00
+5.3848837088973776e+00 2.1965216701109571e+00
+1.1300467301181891e+00 8.2053139297890409e-01
+3.3284866767601171e+00 1.5662024475040075e+00
+1.8030436510045833e+00 2.5298856135969654e+00
+5.6493823578145497e+00 1.8332570621447415e+00
+4.7424333948970876e-01 8.8296216713140740e-01
+4.8986805683825612e+00 2.3704514876560987e+00
+1.0527226819586346e+00 1.2618861346860060e+00
+4.5410604423712151e+00 1.0806785413655708e+00
+1.4343945007580154e+00 1.1272855126910657e+00
+1.3552540238389033e+00 2.5024987945401262e+00
+1.3955263091269214e+00 3.5274742487992561e-01
+6.1203046384516062e+00 1.7085514973547191e+00
+2.4146478716938113e+00 1.4759799812523888e+00
+4.0535086983120259e+00 2.5292063839960695e+00
+2.4651675793170424e-01 1.5654942290773712e+00
+1.4286402013070332e+00 2.3200422103329217e+00
+5.4183433467971671e+00 1.9263195117777554e+00
+2.8437434366011494e+00 2.3536014122631004e+00
+2.2097791888361584e-02 1.8859137986409262e+00
+2.9629498977209598e+00 1.2297143123856815e+00
+5.1608805830082201e+00 1.5250912204616278e+00
+5.4419024354708778e+00 9.5442336152068552e-01
+1.8813529264015605e+00 1.3879774134082898e+00
+1.5734695909449643e+00 2.5872234857252101e+00
+5.2716418761599773e+00 2.1159143203615036e+00
+2.7953364535198895e+00 6.0965126727879471e-01
+3.2336535810111324e+00 6.9247115256472513e-01
+1.1375563129637918e+00 1.3707173546045226e-01
+9.4813132599390570e-01 1.9358670806569300e+00
+5.6431150906588829e+00 2.5206396308206380e-01
+6.2176575099983022e+00 1.4069918258537162e+00
+2.5075830500848015e+00 1.2574745341904401e+00
+5.2002730196240163e+00 1.3820895436715046e+00
+5.9458625723766385e+00 9.1974340679813404e-01
+5.6340017733439272e+00 1.3302382189481876e+00
+5.2957501063304688e+00 1.7863438791641484e+00
+4.3846146701980624e+00 2.3244714076216413e+00
+1.1632617813307815e+00 2.6955283907119378e+00
+1.5399446751823407e+00 2.3663185481957596e+00
+2.7579212367114456e+00 1.7621238765299363e+00
+1.9281337596355952e-01 2.9365752516563028e+00
+1.7413607691677369e+00 3.6193273540305659e-01
+6.1252208466330593e+00 1.9459455433055437e+00
+2.0056363353401059e+00 4.0112303998992749e-01
+3.7647527436890353e+00 2.8140751731372466e+00
+3.8531791355037210e+00 1.7606505474898839e+00
+5.6275214702976299e+00 2.0248732104542517e+00
+2.5161453103205749e+00 1.8402336290041552e+00
+4.3763978956369183e+00 9.6293698510631431e-01
+1.2563776697829130e+00 1.6643625339952908e+00
+3.7481322811091600e-01 2.6473392405648815e+00
+3.9894379148612291e+00 2.6537842904087356e+00
+4.3545668938960791e+00 1.4962884772125935e+00
+1.9143089270547677e+00 9.6798961303001962e-01
+3.5689858543424311e+00 1.2934203822111074e+00
+1.9101184543216467e+00 5.2181633018490103e-01
+5.1290598313068720e+00 9.5156031577195976e-01
+2.2952416739923249e+00 1.0655366097782473e+00
+4.4131529116210748e+00 1.9959325949847457e+00
+4.0345163783887266e+00 1.4930052959590641e+00
+6.0501978883237744e+00 1.2707390441708164e+00
+1.2612128141703509e+00 1.5254228732053625e+00
+3.7307818092814018e+00 1.2491444291245124e+00
+4.9112448312354262e-01 2.1107580840109565e+00
+3.5955782381976364e+00 2.4240689431914841e-01
+5.2138392532629005e+00 2.5173997291726029e+00
+2.2648083420494780e-01 1.7098361847123775e+00
+2.1191397640018206e+00 2.2139546483250316e-01
+4.4748867849377474e+00 1.6003829527193061e+00
+5.3897086972415620e+00 1.3089046251877927e+00
+3.1407887209270191e+00 2.9371997120174376e+00
+6.2195157657500761e+00 9.8787706377949625e-01
+2.6908376864298584e+00 1.0739895275686242e+00
+1.5963997346710819e+00 2.2728738101609993e+00
+2.9261879772374568e+00 1.7775457898692884e+00
+1.0121613716033990e+00 2.0740342803749572e+00
+1.0293407620969033e+00 2.5570306950075623e+00
+1.6589772902276088e+00 2.1659394174815931e+00
+4.7487025205297693e+00 1.5791442488236609e+00
+3.8157816358544596e+00 1.4836315621129044e+00
+6.0207491756530427e+00 7.9076029872695475e-01
+4.8808067346610837e+00 1.7151187227914797e+00
+3.5798621671879038e+00 1.7741833283319637e+00
+2.8478482671608858e+00 4.5821860391080493e-01
+1.8142623754144973e+00 1.9070906311876401e+00
+4.7284657332983411e+00 2.4064442386281408e+00
+5.9022573486019958e-01 1.1685175671812842e+00
+6.4456432396022212e-01 5.2582644303476478e-01
+4.6745306194250773e+00 1.6579567902054337e+00
+2.2935022245780652e+00 1.5493340094430865e+00
+3.4340896984180840e+00 3.0219963560053795e+00
+1.9433003443973997e+00 2.3170648480767464e+00
+4.5136841945972037e+00 9.9553352725122302e-01
+2.2768455344883760e+00 1.3540996334259190e+00
+4.5446020320296752e+00 9.5338528855520432e-01
+6.2736900300005960e+00 7.9660493795991261e-01
+5.2791280672817598e-01 1.1734408534724692e+00
+3.0494087476378056e+00 2.5185049493791909e+00
+5.9897664205741075e-01 9.2053366052378738e-01
+4.5393509372323972e+00 2.4624535699843948e+00
+5.6968105841390635e+00 1.3965485011637906e+00
+5.0147312356669049e-01 1.4330322346787445e+00
+3.3377855316399274e+00 2.9104617533612940e+00
+3.5325248232906530e+00 5.1890931720688749e-01
+4.3840785903525656e+00 1.3014798312748230e+00
+1.9253391013387329e+00 1.2274509358033783e+00
+3.7496441519909167e+00 1.6791510246108032e+00
+1.2607326370121659e+00 1.1611320202537627e+00
+4.2264751063570225e+00 1.6572073619126981e+00
+4.4215396176446831e+00 1.7605194199267786e+00
+3.0179313198799740e+00 2.1857267102869748e-01
+1.0149907286675752e+00 1.5654416121371340e+00
+2.7038809103429831e+00 2.3995658860122813e+00
+3.7416152566002312e+00 7.2628306478623161e-01
+2.0733186642470556e+00 1.8863256260647940e+00
+5.1980256007499737e+00 1.9276491113156002e+00
+7.6246896833347400e-01 1.7734306999293210e+00
+1.5586647068357420e+00 7.5310521335669878e-01
+5.9216706005006117e+00 1.4938379974420302e+00
+4.3629191854692699e+00 2.7790771682080671e+00
+4.7219668528696879e+00 2.0874818057718016e+00
+5.1673173805356374e+00 1.0855424979240502e+00
+2.6769057208609492e-01 1.6823854391622870e+00
+2.5745779099658428e+00 2.0922946900959292e+00
+4.5774223213288927e+00 2.2096254618321272e+00
+8.5159546405104170e-01 2.1824429346841301e+00
+3.9761810978519132e+00 1.6211956457910806e+00
+1.5664479536229867e+00 2.7157886686633610e+00
+5.2761444468415108e+00 1.8337343790533018e+00
+5.2417687218366673e+00 1.8919890040039591e+00
+2.8866647678987132e+00 2.0075462976236560e+00
+2.4809444754562420e+00 2.0477336728859821e+00
+6.0615027696732460e+00 1.0653557391808226e+00
+2.7429652313713415e-02 2.3771881480394823e+00
+2.5349651934485493e+00 9.1977126869129489e-01
+3.0405882740781740e+00 1.9324109164995238e+00
+3.8364965851094892e+00 1.8799054267113862e+00
+4.1034874440181390e-01 1.2476461228403242e+00
+8.7337459714809729e-01 2.7649924258641674e+00
+4.1140280962154518e+00 9.6732150321882671e-01
+1.3864794824741715e+00 1.1443096071066705e+00
+7.7489543887205148e-01 5.2195140033232379e-01
+2.6267666898299380e-01 2.6652275842112112e+00
+9.6571650435668221e-01 1.1778406813071975e+00
+3.1850815787356312e+00 1.0637338807103409e+00
+5.4370527380003315e+00 8.0725703850886366e-01
+4.8030176987721536e+00 1.4798632762889889e+00
+8.6187498268884832e-01 7.8894072880953403e-01
+3.1669485790339102e-01 2.7040597413933805e+00
+4.2420544279598555e+00 9.6819337531162997e-01
+2.9884068143009594e+00 1.1831072640699023e+00
+3.1229302177172973e+00 7.5239579321998229e-01
+2.0125989778653386e+00 1.9036493244559505e+00
+2.0625008365639843e+00 1.5443887844841115e+00
+5.7155916732362222e+00 1.6596241897708814e+00
+2.8724565896186278e+00 2.2894848114939377e+00
+4.7856014571324206e+00 7.5837131111934708e-01
+1.2598209991991480e+00 2.7083094591675563e+00
+5.6625235324006740e-01 1.4827526072487396e+00
+1.2981632177929556e+00 2.1564941855553923e+00
+3.5066208210787635e+00 6.3339985421165013e-01
+4.2198156454778033e+00 1.5095022506435116e+00
+5.9126733842261299e+00 2.4992999664256921e+00
+2.5335963137270623e-01 7.9465916278468907e-01
+1.0184315084838143e+00 2.7115831964980197e+00
+2.7854413964714744e+00 2.3858374418645871e+00
+2.1034478503243110e+00 2.1882407911383690e+00
+6.1008729299428799e-01 9.9433539977103580e-01
+5.1055945687440349e+00 1.7990596918836457e+00
+2.4814047411826659e+00 2.8157645863068375e+00
+4.1655305237408999e+00 7.5778938487024605e-01
+3.5677834689215642e+00 1.8230392627274266e+00
+6.2672463837339842e+00 9.5137400097735325e-01
+4.1349081177678189e+00 6.7581056200232315e-01
+2.0597474938030742e+00 1.1979185435435009e+00
+1.6650839370746491e+00 1.4592035341452343e+00
+2.8123415685897806e+00 7.0487694377742105e-01
+3.9141528610445242e+00 2.4662292813420050e+00
+4.9897373789041177e+00 2.4741240082892357e+00
+3.4936410094350556e+00 2.7705020610735516e+00
+1.4068366092893259e-01 1.9269209925474864e+00
+4.9904312685679679e+00 2.2583213370527826e+00
+4.5565785854668457e+00 1.1772911848562329e-02
+5.1360539805977972e+00 1.7363667771233493e+00
+4.3322115240613632e+00 1.0426932520599839e+00
+3.7609016234355650e+00 1.1409118104354081e+00
+2.4173212262142236e+00 1.0271087783192685e+00
+3.6259309282217878e-01 6.3523068548778328e-01
+6.2805483553093850e+00 1.9268783830543441e+00
+4.2273785406750717e+00 2.2479929047950504e+00
+1.4948185299697738e+00 2.5603921444742621e+00
+5.7477443074566370e-01 1.3952494381368412e+00
+1.4830790636404507e+00 1.5116414797555391e+00
+5.6090704749929143e+00 1.2817344875580035e+00
+1.3575232539184827e+00 1.3918296996736244e+00
+2.9570802441312147e+00 1.7245544639369985e+00
+5.7524710258326550e+00 2.2913606227920678e+00
+1.2156173521989981e+00 9.6448596072470627e-01
+3.0652249061453443e+00 2.6491745190684712e-01
+3.9050917205525586e+00 2.3892962479771955e+00
+3.6664982101199284e+00 8.5365769739789421e-01
+4.1819875608226607e+00 6.3706754187754144e-01
+1.6631146436221911e+00 2.5959998081554860e+00
+2.6563783218793304e+00 1.5940229635875731e+00
+5.0962566449525060e+00 1.0839344698381148e+00
+5.8815008006241030e+00 1.1093965331323048e+00
+2.9246340859842190e+00 8.8657405863125216e-01
+1.9248088863926165e+00 1.5059479004498875e+00
+2.7322615015532326e+00 1.5057691606549368e+00
+1.9632576028822710e+00 1.8719413281701915e+00
+1.7731285182296728e+00 1.4491169031399656e+00
+9.3231849156054469e-01 2.8561919127046762e+00
+1.0403174207188757e+00 1.0896995256919801e+00
+3.0515004246903548e+00 8.6608801138214675e-01
+4.9173166801696144e-01 8.1976759334528915e-01
+5.7581515265857668e+00 2.7928863013975711e+00
+8.7975571345191916e-01 5.6626792870753051e-01
+1.0536646245180075e+00 2.4828104388328573e+00
+4.5400016647875070e+00 7.6250404822835427e-01
+4.2942589429384617e+00 1.5382967885049377e+00
+2.0472061415143643e+00 2.8652442615006843e+00
+5.6711079356578953e-01 2.3866765827292165e+00
+4.4682278559436295e-02 1.4216878572969720e+00
+2.3448763140352287e+00 1.1008833116058780e+00
+4.9703632699322489e+00 2.7427311102942511e+00
+2.8458802096591156e+00 2.2541217747042794e+00
+6.3451091189350084e-01 2.1342187895408760e+00
+5.1540491914253970e+00 1.1249935501513089e+00
+4.1806134364480938e+00 9.6146200923774938e-01
+2.8453930354332750e+00 1.3796074306915034e+00
+3.3124032638367931e+00 7.1303942623466043e-01
+3.2768707206589585e+00 1.6594231878566419e+00
+3.9766350343530128e+00 2.6644351197352112e-01
+4.1522614327251119e+00 2.1099672328547658e+00
+1.2239825078479292e+00 1.1995073479298743e+00
+5.1224687742536039e+00 1.4079647420640862e+00
+1.1889832216262142e+00 2.1113990768111948e+00
+2.6310326670410795e+00 1.9901619318583534e+00
+1.4903105591059955e+00 1.2103101084879826e+00
+1.7304828138281019e+00 8.8585652374839474e-01
+6.1847073226890084e+00 2.5858451355611818e+00
+5.7755537063229472e+00 1.8622759923836814e+00
+3.2603791658717705e+00 9.4232776046881239e-01
+2.9068487726693606e+00 4.1436188053230749e-01
+3.0965798912693354e+00 1.3491570849467438e-01
+2.1715426897153391e+00 1.7333163800830058e+00
+2.1391210672952785e+00 1.7026732979180654e+00
+6.1810717559934965e+00 1.8580398292959879e+00
+2.9282573051707295e+00 1.6632053523866754e+00
+4.7425692915517557e+00 9.5894699795115268e-01
+4.1761130867791394e+00 1.0468564641372664e+00
+5.0544510880738649e+00 1.9592716213083849e-01
+1.6720201073124283e+00 2.2322867470195646e+00
+1.7715820864636695e+00 1.8001778231998107e+00
+4.5271652494968784e+00 1.1585561542325200e+00
+1.1308371800553223e+00 2.3113508243773455e+00
+5.2712503510433182e+00 1.9397210493109931e+00
+1.0896990930896360e+00 8.8674407337940542e-01
+5.9309884417891237e+00 2.1668990788786053e+00
+4.5656551192131269e+00 1.9681492918435373e+00
+8.3366583325491028e-01 1.6564697756521813e+00
+5.3857242509644809e+00 1.8852452759326983e+00
+5.8607163114653025e+00 2.6125844160642337e+00
+3.4583850869663886e+00 1.5649794752584376e+00
+2.5087355016378758e+00 2.0030929864773741e+00
+2.8164473970300632e+00 1.4354116137154538e+00
+4.4912120711938002e+00 2.7703076892080327e+00
+1.6148925564898891e+00 1.3471100347239200e+00
+1.7234849181881007e-01 1.7069283998515994e+00
+5.9352272290888308e+00 1.2981418681396097e+00
+6.2721034692450548e+00 1.9750611374045002e+00
+4.9013812533759458e+00 2.0239971063287410e+00
+6.1960634649101571e+00 1.6815905830910396e+00
+2.9940044930696748e+00 1.4668783308827682e+00
+8.0821059538542839e-01 2.3652936675917173e+00
+2.9607064608878928e+00 2.3600734488591297e+00
+4.7091332622490061e+00 2.1358010257980160e+00
+4.2935921910786146e+00 2.4720493432861428e+00
+5.4817693733755437e+00 2.4796887927197289e+00
+4.7435901797572191e+00 2.2428358489171236e+00
+3.9202396270457500e+00 1.1429129977946972e+00
+1.7490441984778182e+00 1.5032325194133005e+00
+2.1578887497117174e+00 1.5281803405640726e+00
+5.2193575544857591e+00 4.9927044328659531e-01
+2.4687860779179784e+00 1.3019334609373772e+00
+4.8692030582805046e-01 1.1391006292373198e+00
+5.4420015628957570e+00 1.7205496969351894e+00
+2.6973961699159679e+00 2.0545254547109129e+00
+1.1793348947973719e+00 4.8047917706140320e-01
+1.0699375409841672e-03 9.0388497174476834e-01
+4.0831113091306941e+00 1.0838269805897713e+00
+2.3423285240015268e+00 2.5962061693692968e+00
+2.4653245714715162e+00 6.2260588357425306e-01
+2.7636023593760788e+00 1.2455034521207262e+00
+3.3826286250443514e+00 4.0891889844534091e-01
+1.8817964388866610e+00 1.4753773009288822e+00
+2.9690673816613908e+00 1.9470629959589429e+00
+6.2015898230267332e+00 2.5209074101912652e+00
+1.0408573951915265e+00 1.7605599338332896e+00
+1.9191159925117252e+00 1.8144591082587762e+00
+4.7211000766924105e+00 7.1946051298758018e-01
+3.8205175260987208e+00 2.4531333834568958e-01
+4.4652687882364450e+00 2.7086924364764116e+00
+5.0295367789232674e+00 1.8385029596376403e+00
+6.2677679282301311e+00 5.6431953361635756e-02
+7.9122039615868189e-01 1.7253796968883968e+00
+4.1560683717151816e+00 1.8272316052576905e+00
+4.7162266898261294e+00 2.4706511264391198e+00
+4.4174034172219834e+00 1.3394046308042087e+00
+3.7842532233776547e+00 2.3825467278430561e+00
+1.8084650624781462e-01 1.8388864850289393e+00
+1.2108181513502434e+00 4.3125460228685397e-01
+2.0749456198336134e+00 1.1448447918700200e+00
+4.1061129151763396e+00 2.1897912692634645e+00
+1.3320761158324896e+00 1.0859540823874534e+00
+4.6861921898741699e-01 1.9354842879921876e+00
+6.1503539637970865e+00 1.8198468068950340e+00
+1.2503620123053498e+00 1.5968080280829011e+00
+6.2070435221429108e+00 2.2032101431043682e+00
+3.8052231132155874e+00 2.0053686157422463e+00
+1.5568480747882478e+00 2.0019545885814454e+00
+3.2148955028553274e+00 1.6623942381257684e+00
+4.5579993273599992e+00 1.5905359187983739e+00
+1.6025001996709247e+00 5.0897321318224886e-01
+6.7327670555053132e-01 8.6994636474315823e-01
+3.3674603446018776e+00 2.0028162499365387e+00
+1.4274495750432599e+00 4.1185289168212069e-01
+2.0133979558835229e+00 1.0986616240112856e+00
+6.1516379547476507e+00 7.1445790850733393e-01
+1.9820763132465613e+00 1.4940569633928744e+00
+5.9221847200363094e+00 2.6827474941246150e+00
+2.6054588152731686e+00 3.9205055515899545e-01
+2.7106386032006657e-02 1.2058118291177233e+00
+1.8588965039167160e+00 2.5717016849832621e+00
+9.8753867190030675e-01 2.7037073546863910e-01
+6.2394043867973075e+00 2.0145062058645071e+00
+2.9624595994368392e+00 4.6716752706665465e-01
+3.3724427237947792e+00 2.3055867989757513e+00
+1.4203586255603471e+00 3.0798401794846146e+00
+5.4404223639996010e+00 7.1437896441207627e-01
+6.1782289093478138e-01 7.7177019368988697e-01
+5.1554977959613568e+00 1.9091697730401171e+00
+2.6220163065296060e+00 2.4851804775643958e+00
+5.7922611169130356e+00 1.3044236647586711e+00
+3.3019539018181323e+00 1.7763219276378313e+00
+5.4090234439647400e+00 5.4271348633402261e-01
+2.2473380391161486e+00 7.1436894627548519e-01
+2.5406989952483858e+00 1.3544729350231608e+00
+3.6964728538572493e+00 5.8454837785882130e-01
+5.7232210612162540e+00 2.1667489243675115e+00
+8.4787338969714643e-01 2.0819732188006093e+00
+1.5115480820859117e-01 3.0237252052331831e+00
+2.4097473816412709e+00 2.5221353475101891e+00
+3.7520352501965517e+00 1.3038681957851148e+00
+5.7583782151904801e+00 1.9028385502174083e+00
+1.8834395572811384e+00 9.2455061046230425e-01
+3.3849278200750907e+00 1.2371637635862283e+00
+4.9447583753393136e+00 2.2337154484116679e+00
+2.1247591688397147e+00 2.0240488736215969e+00
+5.5148984518713817e-01 2.3437048770168269e+00
+2.7461646580723209e+00 1.4548566130755392e+00
+1.2841976875066807e-01 2.3558340593860967e+00
+4.2536696257378681e+00 7.6218798045763758e-01
+7.4407835493397290e-01 2.7783269380396032e+00
+1.2517918195085080e+00 7.9525988933678160e-01
+3.2348467689682074e+00 2.1288121185848010e+00
+3.9936667211104604e+00 9.7115977114032437e-01
+2.3460427366721115e+00 2.6470807280739450e+00
+3.7853560200128675e+00 1.0986130184597267e+00
+5.3706882681431001e+00 2.2638193072493955e+00
+4.5454124593593841e+00 1.6170260046123341e-01
+2.4831585761258990e+00 1.5555953352046776e+00
+3.5805779184694266e+00 1.5023855394994960e+00
+1.5724267266277441e+00 2.0650643198402197e+00
+4.5775216373347272e+00 1.8229536635426931e+00
+2.2026504097720587e+00 1.5480960428265897e+00
+4.9795582006540151e-01 1.5210777439274250e+00
+1.9112438316337603e+00 1.6467176209993608e+00
+1.8892483630361365e+00 2.3588965745442851e+00
+1.5054917430036641e-01 2.1763207565598743e+00
+1.1228482646166151e+00 1.2675848169197759e+00
+5.3868827660292791e+00 1.3616250605032119e+00
+4.0894159961970624e+00 2.3175324485574791e+00
+5.3369090629921878e+00 7.5699864437659992e-01
+1.7405170234891880e+00 1.5498725343059974e+00
+5.5898547916068715e+00 3.1412404261245208e-01
+1.2215815078862344e+00 2.0779587633716874e+00
+5.9813276071840003e+00 4.5490461420354511e-01
+2.5874317647715590e+00 1.3895180852177285e+00
+1.4532507379979160e+00 9.8952334708994105e-01
+5.1339887139151967e+00 2.6249075208295358e+00
+2.3027930773805529e+00 1.3858757034256870e+00
+3.1568773101017795e+00 6.7105675251215724e-01
+4.7190159485928191e+00 1.5404051661019869e+00
+5.2453352845514027e+00 1.5147121758266149e+00
+2.4746356133607246e+00 9.2694180805761284e-01
+5.9953778084263698e-01 1.2712671448195489e+00
+1.7120780217007867e+00 1.6682966345483883e+00
+5.0348360272092911e+00 1.4500029537461132e+00
+5.8809639227435495e+00 1.0442044739390337e+00
+3.5117788379105015e+00 1.5701922073180585e+00
+5.8258953483685536e+00 2.4359062299764753e+00
+3.6458806893686058e+00 2.0217763334821139e+00
+6.1719508270368335e+00 5.1566620855851175e-01
+4.7919795196292041e+00 1.4026651865337059e+00
+1.1143622288896884e+00 1.4636898916661807e+00
+3.2774961184182483e+00 5.0533352699058498e-01
+4.0881792056154289e+00 2.1230638372429569e+00
+5.3774311053570631e+00 5.8624042284259659e-01
+3.7095913047100666e+00 1.4233713402900072e+00
+2.2593269068305055e-03 1.1034284930035478e+00
+8.3487640287537124e-01 1.2149263440288107e+00
+1.8173995712845661e+00 2.4412580925798815e+00
+3.1201070146200176e+00 3.2308277304546662e-01
+8.3526260381112971e-01 1.7465036588620861e+00
+1.4236957711827800e+00 2.4899034128693502e+00
+1.2268815171453735e+00 2.4296774116422069e+00
+4.0534931703363100e+00 3.0136745574244683e-01
+2.1173837828778197e+00 1.4353271488871553e+00
+4.9559050021768485e+00 1.6737336400944618e+00
+3.4024186856505798e+00 1.1870859892365739e+00
+1.4221253606359294e+00 2.2032118211441989e+00
+4.3588468836068497e+00 1.1439390002962402e+00
+2.6735729381986362e-01 2.1383233545432336e+00
+3.5693771191978128e+00 1.2359200867219027e+00
+6.1858394623468262e+00 3.8889250218265303e-01
+2.2911796501775274e+00 1.1125840985739508e+00
+3.7159126825859197e+00 3.4922208117128051e-01
+5.8305454993155870e+00 1.0193445660265446e+00
+1.6395554171848401e+00 1.5025001010498489e+00
+5.2482140056186815e+00 1.6356173010096835e+00
+1.0928853869066755e+00 1.2996454413150187e+00
+5.5143191001111163e+00 1.9165199629363752e+00
+1.7634989085503323e+00 1.6922471169873923e+00
+5.3432609007893230e+00 1.2695052251732757e+00
+5.1206504726493733e+00 8.5547453641172022e-01
+1.1648607411818581e+00 1.9540205151169303e+00
+6.1558343705485283e+00 1.2972692079374972e+00
+1.5757645897807269e+00 1.5540727578834770e+00
+4.9337083062869596e+00 2.3236635868076356e+00
+5.0507695018404410e+00 1.9454984764771492e+00
+3.1314598158923772e+00 2.4345629443967427e+00
+5.7422589282378906e+00 7.1898555884580351e-01
+6.2439075568729896e-01 1.2318016616797349e+00
+8.6641821043526379e-01 8.5466223515358841e-01
+5.4162833773424230e+00 4.4924057030059394e-01
+9.0072430406801018e-02 1.3586335512514809e+00
+4.5982631497476119e+00 1.5038599677692113e+00
+5.1657260404416929e+00 1.4704672274149699e+00
+6.4858978252065880e-01 2.1360945330267733e-01
+3.0599866892078853e+00 1.3009560456083800e+00
+2.1918163957890124e+00 8.0262156739642232e-01
+4.9000610472134021e+00 1.6167298813099451e+00
+1.7792614715294659e+00 2.8888695247608589e+00
+5.1780734763598373e+00 2.0968045369253829e+00
+1.0330384842932889e+00 1.4875259645836492e+00
+5.3657000609716308e+00 2.4399911093919986e+00
+5.7706014969811505e+00 1.6527642203365094e+00
+1.1701854100409117e+00 2.2651668780919638e+00
+3.0615123455926163e-01 1.9541860531392141e+00
+3.3822106816073152e+00 2.4579912370652477e+00
+2.0883856807849988e-02 1.6863875720377783e+00
+5.2207589331629309e+00 1.0993637135778747e+00
+5.6856999298235227e+00 9.7008210837026432e-01
+4.3710916249414975e+00 5.1715397211391556e-01
+1.4884964457255361e+00 1.0638236442557027e+00
+3.6608879077650203e+00 1.8762966742800276e+00
+5.8741884540070712e+00 7.4255618310286631e-01
+5.6084116883142245e+00 1.5328715520514102e+00
+3.8478103068517724e+00 2.1036742121859140e+00
+5.0303546340751133e+00 2.2160380598112170e+00
+5.2021067144992328e+00 2.7249341905754987e+00
+3.5126739063499031e+00 2.3082907464125086e+00
+6.2631960930173420e+00 5.0002405983296083e-01
+1.5153912558892539e+00 2.4607741195029940e+00
+2.7465758247820800e+00 8.4874685348517998e-01
+2.7476706886586295e+00 2.3489553568916732e+00
+5.5015945194258018e+00 8.2280885191355391e-01
+3.7931645958036597e-01 1.3608110659137378e+00
+2.2042779000420794e+00 2.2746810427933015e+00
+1.1297984850265372e+00 9.6304412829815345e-01
+1.6149446152046063e+00 9.0840274557023126e-01
+4.4781206202851793e+00 2.4928227870435204e+00
+1.7095733187455007e+00 2.3021427583636660e+00
+1.4785645508979448e+00 1.3985995731156420e+00
+8.6157095063939471e-01 1.9367134983265057e+00
+1.4004751591055307e+00 2.1053391203309668e+00
+5.1895523610582073e+00 9.5249213678756239e-01
+2.4067351036915112e+00 1.3393471483024333e+00
+4.1598342593569928e+00 1.1569332195320254e+00
+5.8316721549257062e+00 1.1426156501761406e+00
+2.7245700451553763e+00 5.6272282617523040e-01
+3.7722675061416604e+00 2.4292153877573104e+00
+6.1021824990680784e+00 1.4000552490546065e+00
+2.3272810149416707e+00 1.5794885736599278e+00
+4.1731694782385684e+00 1.5323178506513302e+00
+1.9867007386403555e+00 2.1775592875456153e+00
+5.5333701656998056e+00 1.0554274442473139e+00
+1.8458734269423822e+00 2.0900626619479743e+00
+1.8401954797781541e+00 2.2088665321420868e+00
+5.6794055577141229e+00 7.1694642320570212e-01
+3.1197428365614410e+00 2.7658763890532230e+00
+1.2484655736106427e+00 2.7628852851832586e+00
+1.4053229021128910e-01 1.6546458454753814e+00
+1.1343607516846728e+00 2.5791541881071227e+00
+1.9922221358585299e+00 2.8892649149679528e-01
+6.0543782673250313e+00 1.3647361918317233e+00
+4.9635535353080211e+00 1.2574851849459407e+00
+4.3109352033321633e+00 1.9787979197221337e+00
+5.2933052774964864e+00 1.6234886778102704e+00
+2.4857434457732022e+00 2.9039448292523931e+00
+4.4644202942002478e+00 2.2274900418149501e+00
+3.0240745007331506e+00 8.1608254707183225e-01
+5.2340058226971253e+00 2.6146614177464507e+00
+2.7189057610994327e+00 1.9371846230707843e+00
+5.3854831491506863e+00 8.5115194458114374e-01
+6.2750807510754525e+00 1.4026955524268796e+00
+6.2266709071019708e+00 5.5738285153025946e-01
+1.7564162214635719e+00 6.8157556165084743e-01
+1.8893397723825522e-01 2.1484826593449244e+00
+9.8986523795450676e-01 3.3143497574783054e-01
+2.9693558784353353e+00 1.6648462094027245e+00
+5.5744853381172668e+00 6.5309898920246434e-01
+1.8088692878931136e+00 1.6387163316439151e+00
+4.8655198909169517e+00 1.4069719036463635e+00
+1.1198195242716400e+00 2.9368257761991923e+00
+4.6145280123029817e+00 1.0860592539613592e+00
+1.5350506492072651e+00 1.7048896052122373e+00
+4.3294675838730567e-01 2.4021844083142971e+00
+5.3202873174816290e+00 1.3631129864675209e+00
+4.4652130106960310e+00 3.0013582184207421e+00
+5.9683132376210901e+00 4.9634447982198848e-01
+6.1789195503374383e+00 1.1804689156669326e+00
+3.3311608547477425e+00 2.0560444741811468e+00
+5.6514958260308665e+00 1.0345831526014837e+00
+3.0240548780456726e+00 1.2178200789740643e+00
+5.5847516565799014e+00 1.9955426203320328e+00
+1.2844808444966471e-01 1.4816646752038405e+00
+7.6317532477861683e-01 2.6415330983026299e+00
+4.6324741937257325e+00 1.2523026852742973e+00
+2.4380208956098679e+00 5.3735788444578558e-01
+2.5210765524191827e+00 1.6644334703840753e+00
+3.7576630665356929e+00 1.7934543720247735e+00
+3.3235315609066340e-02 5.5523189570451903e-01
+2.2771406131476981e+00 2.9454880085314574e+00
+2.0605608411917054e-01 5.3147570884652917e-01
+4.3895780070237116e+00 1.1941957506018843e+00
+3.2430578380668780e+00 8.9982930389318727e-01
+3.7625555498476659e-01 8.0398874714447510e-01
+2.4708193551524045e+00 2.4461164413395267e+00
+2.7144170454581853e+00 1.1195067913571699e+00
+4.2059774660609897e+00 1.4366699978124895e+00
+1.3553992443119254e+00 1.5018900193896383e+00
+5.2627531562577756e-01 1.9590218586723063e+00
+2.9309278076676644e+00 2.2506966585057917e+00
+4.6216202894717613e+00 1.9461434541228699e+00
+3.8090344838855872e+00 1.1920941380867114e+00
+5.6586252682339344e+00 1.5162892963924577e+00
+1.5669567707442997e+00 1.3513227394892513e+00
+1.4203879659092562e+00 1.0292519247544019e+00
+1.4868630376445544e+00 2.3249883353340119e+00
+5.1103821871372128e+00 1.5998702752825342e+00
+4.4313149096943043e+00 9.8948627365593922e-01
+5.4474844822950832e+00 1.9729537328515185e+00
+4.4748031876525793e+00 7.6323742208616041e-01
+4.1411723882135396e+00 1.2013161981402263e+00
+2.6530062416478626e+00 1.9109624452485292e+00
+3.4632426332109914e+00 1.4749547206249718e+00
+6.1785790960510818e+00 8.4737944177104008e-01
+6.0228628548195431e+00 2.0648622109900412e+00
+2.1536637227738487e+00 2.8295521037229174e+00
+9.8268176549883268e-01 1.3432047819324380e+00
+2.6354069153076907e+00 1.5087459478325744e+00
+3.4748579217664508e+00 2.1745831564218610e+00
+1.4250092870427178e-01 5.6058270049551351e-01
+5.3022268432461903e+00 5.3461425340129476e-01
+5.5971392109081357e+00 1.5727198285682711e+00
+3.0567934380380635e+00 1.1013712137826785e+00
+1.3107017343146199e+00 1.5235934728529930e+00
+4.5100003760014022e+00 2.0454413812887289e+00
+4.6664738662365455e+00 2.9268593254040542e+00
+2.9152699805055997e+00 1.9475445247098244e+00
+1.0153796003380835e+00 1.2357429041857473e+00
+4.9924835367446736e+00 2.3036190232540701e+00
+5.5435351824251677e+00 2.0942344394379111e+00
+8.8012929674486440e-01 6.9411574047863311e-01
+5.9203190889691903e+00 1.3497027054997792e+00
+2.7742205668237041e-02 1.3342537261587017e+00
+2.7111346139864962e+00 2.5885545724132588e+00
+9.3590163882349309e-02 2.1697210653067613e+00
+1.4758954209116308e+00 9.2389725531770828e-01
+4.4636496417197478e+00 1.3157942046358624e+00
+4.3909531413314689e+00 2.6742751969998704e+00
+1.6549190240941003e+00 1.2603707325797284e+00
+2.1138703090761979e+00 1.5414743839888632e+00
+6.5674695559928775e-01 4.1566311459813599e-01
+4.1253614676573491e-01 2.8932617340987532e+00
+4.8019357397429001e+00 1.1197613369486501e+00
+2.0696289612462566e+00 1.7847214853408691e+00
+3.2604208929877418e+00 1.1472454759924480e+00
+6.0378267295674481e+00 2.5813024904237838e+00
+4.2365751773867277e+00 1.0140351810746515e+00
+2.9422953849460169e+00 2.6774497801313295e+00
+4.0577775030582854e+00 1.2539950992168909e+00
+4.2328359072554029e+00 1.6119023508840318e+00
+1.3766175176505935e+00 1.5429017494161539e+00
+5.0698703723479257e+00 1.0417945900658432e+00
+3.1224664929032127e+00 2.0109479995369912e+00
+2.7214481151178100e+00 2.1189394958224486e+00
+4.4553164615809289e+00 1.0294119457493327e+00
+3.6259903335168389e+00 3.9166688013219386e-01
+1.5928601503425457e+00 2.3332542826090004e+00
+5.2631101986126483e-01 1.8295027993458837e+00
+3.6190880688651159e+00 2.4969176221046347e+00
+4.1864405218430676e+00 4.8102927823440078e-01
+3.5872044815607262e+00 8.9653298298190409e-01
+3.3071978564662876e+00 1.0562248307203779e+00
+2.9482136069563105e+00 9.2915073294635020e-01
+2.0640185094190269e+00 1.3784640907879742e+00
+2.6501954601088973e+00 1.8563432587512003e+00
+4.8550629757052928e+00 2.3361034575391231e+00
+6.2804346867726117e+00 6.6806476477543042e-01
+1.6095585245020256e+00 6.2783612761328911e-01
+4.9640093230186606e+00 9.8086881252679159e-01
+8.2695134105183943e-01 1.8954060519048570e+00
+1.8746093393311014e-01 1.5656403349449988e+00
+2.8766621354188295e+00 1.2182261630442335e+00
+3.5205787241391784e+00 1.9697642298625166e+00
+1.4114414426102337e+00 2.3777908481427956e+00
+9.1668920162037981e-01 2.0152470984066757e+00
+4.3636483696745554e+00 1.8709159034532397e+00
+2.2130638339416988e+00 2.4154709866861723e+00
+3.1035830784140792e+00 2.1892067108363298e+00
+4.3708259612898424e+00 1.4573136897413670e+00
+3.7586513325527453e+00 2.6967136642722318e+00
+3.1105272291485644e+00 1.7140881441231324e+00
+4.9012512654205569e+00 1.1213935585612129e+00
+4.3042910978978961e+00 7.0351142503334851e-01
+2.0230748529736671e+00 1.3326055937238825e+00
+5.1765441591593833e+00 1.6094098388932503e+00
+4.1573962375192819e+00 8.0583212627660161e-01
+3.6410314942823185e+00 1.9165359105776074e+00
+9.5468892252892346e-02 1.8008025849364229e+00
+5.2516686510481598e+00 2.3732037787962263e+00
+7.5000706289914509e-01 1.2847348151577114e+00
+5.2415617838526245e+00 2.8444012245131844e+00
+2.1368059247653743e+00 2.1246554887769111e+00
+2.8219711369985774e+00 2.5052616264147609e-01
+4.9674567103218816e+00 5.0018185106905566e-01
+9.2875352029936309e-01 2.1920746101183797e-01
+3.7812095873867122e+00 2.6054584334618909e+00
+5.8539460635203486e+00 2.1318675499311324e+00
+4.0019929742762796e+00 1.6798636663179658e+00
+2.9423871923790212e-01 1.4746895511087972e+00
+4.4163298276610723e+00 1.6383295521478398e+00
+1.5742601836100110e+00 1.3003987601310192e+00
+4.3880339041409071e+00 1.5350534481697025e+00
+2.3254645313735907e+00 9.4258462645297525e-01
+3.4229276966387960e+00 1.0827630672239004e+00
+1.3457294089548519e+00 1.4560267101237012e+00
+5.6206152708710739e+00 2.3456175447826286e+00
+2.3775162245378314e+00 1.3030785192047838e+00
+4.2153445576624993e-01 5.1779092469351018e-01
+1.5765310510684705e-01 2.2222878517582823e-01
+3.9259689822048429e+00 1.0957583294707096e+00
+5.8974002388653197e+00 2.2512670816275744e+00
+2.0318583325127584e+00 7.8195475106648327e-01
+4.6482985050016632e+00 2.3752726339497654e+00
+1.8331950232420307e+00 1.0087257560465224e+00
+8.0043527311575513e-01 2.6874528003525002e+00
+3.1910434579575151e+00 1.2299536739251158e+00
+2.4411212146017647e+00 2.6265263431660903e+00
+6.1479831759316070e+00 5.7481965407495383e-01
+1.8902221315343035e+00 2.2943097961470116e+00
+3.7753255981426417e+00 2.5296666881930232e+00
+3.5932773562932594e+00 1.9975428097031753e+00
+3.8515077932673698e+00 1.2110618775055846e+00
+3.5408940350745963e+00 2.2259339625448717e+00
+1.8598586736716740e+00 3.3476190370288039e-01
+1.0348075305720239e+00 2.7694235802027047e+00
+4.6341541525956425e+00 1.6145242318416171e+00
+3.0629007927720986e-01 1.3064908513008633e+00
+5.0470025393695490e+00 1.7534624693782135e+00
+2.5794475728536055e+00 2.6352305736317461e+00
+5.2210708358337810e+00 1.2851103975201119e+00
+6.0811913579184065e+00 2.1350757891585617e+00
+2.6631543504119954e+00 1.3173541910053022e+00
+2.9594479273910368e-01 1.7848699391724034e+00
+4.9708638499145330e+00 9.0780396144019249e-01
+4.0403483031836620e+00 6.5017298876558405e-01
+3.1472784726592362e+00 9.8472133753708957e-01
+3.5307790670950747e+00 1.0959701019149337e+00
+1.9707449591511983e+00 1.7299458251681388e+00
+4.7224354131386175e+00 1.3103014657304062e+00
+2.0824408318491829e+00 2.5725252864261403e+00
+5.5488167594270212e+00 1.8065085282298121e+00
+2.1820282612945450e+00 1.6645472730948503e+00
+5.0024704109175442e+00 1.7495279832424473e+00
+5.0603799847941389e+00 1.9901997029352902e+00
+2.3522306862664077e-01 2.1775546189718886e+00
+2.5561088806998740e+00 1.0887087991643807e+00
+6.0710948591199942e+00 7.1283568834084021e-01
+2.5962810803884753e+00 9.9468178540106122e-01
+2.0606886200919954e+00 2.7694067842175958e+00
+4.0135305864321378e+00 1.4394191375963354e+00
+5.4279500564113423e+00 1.3772222480617724e+00
+4.9926987048009677e+00 2.0930835823274188e+00
+3.3603252846190239e+00 1.7584100819058432e+00
+4.7029739548555591e+00 9.0827954702909819e-01
+4.6388574271055081e+00 2.0335862525202160e+00
+2.7697898379245496e+00 1.5979163217597321e+00
+5.3723817179390654e+00 9.5524182504573896e-01
+3.0734445298323214e+00 1.6432546167499995e+00
+5.9559335125202431e+00 8.0970259401789157e-01
+5.1038311884932455e+00 1.2017761649203385e+00
+8.0429658900207279e-01 2.5384164496408350e+00
+1.5475355738666965e+00 2.2840921141732107e-01
+8.0749974573788474e-01 1.4676007265208071e+00
+3.3259952056851501e+00 2.2175593908150875e+00
+3.6885041915806127e-01 2.5112185044948259e+00
+2.9153037368340615e+00 2.6230312816272487e+00
+6.1178434056978590e+00 2.8553448125456047e+00
+4.3701702412399417e+00 1.9682016213858640e+00
+5.4786777728202134e+00 1.4718880044989320e+00
+3.1060475708080624e+00 8.3918155157988228e-01
+1.8267449728693053e+00 1.3068780970907672e+00
+1.1482117191839523e+00 1.3960659264458337e+00
+5.9923215354506665e+00 2.1531036277433913e+00
+4.7853102187537022e+00 2.0863187688481695e+00
+6.0016409962322577e+00 1.2649726851182486e+00
+4.2917320653089259e+00 1.9373977968700375e+00
+1.1753440486072670e+00 1.4929100800358313e+00
+1.7665270311705807e+00 2.4791756476129905e+00
+3.4756860304651305e-01 1.6191635209403583e+00
+2.5950550601501199e-01 1.6615149247601257e-01
+1.7251401456903503e+00 1.7759524809041309e+00
+1.1184341933275190e+00 2.6275920786550193e+00
+4.3485833996989731e+00 2.0160307500407075e+00
+5.6328249315497700e+00 1.4730141079496342e+00
+2.2324666704309606e+00 1.0794677816377369e+00
+6.5071578040092692e-01 1.9637619892793561e+00
+4.9968641298626642e+00 1.6397959527123078e+00
+2.9162541086783769e+00 1.3108089211578720e+00
+4.2946622879648082e+00 1.0065360734947462e+00
+4.3166769034309942e+00 1.1686550802538742e+00
+2.6745874116228867e+00 2.3545888455311421e+00
+4.1732395587254274e+00 5.7989421099160643e-01
+3.9963633097601985e+00 1.8138037939113507e+00
+3.2640398820896093e+00 1.5331200198983093e+00
+1.0632877528114015e+00 1.7968321156573275e-01
+3.2968451206615246e+00 3.8394152012787841e-01
+4.7554537622585737e+00 8.9231139898905953e-01
+2.6629608844796544e+00 1.7022260128355260e+00
+4.6682147979419044e+00 8.6598120475625651e-01
+4.3931476791313075e+00 1.6752570311141501e+00
+6.0026068149027489e+00 1.7954251540337680e+00
+1.9683380733288713e+00 1.1440821109315913e+00
+2.2677316161690015e+00 2.0984420864514091e+00
+2.4754687565764559e+00 2.2143696328016746e+00
+1.2697742973214154e-01 2.1205367589897177e+00
+3.9955706304941550e+00 2.0442037479683242e+00
+3.7388390435216836e+00 2.7437471059129561e+00
+2.3138146768852255e+00 1.4648616588707692e+00
+2.6783003444090516e+00 5.1049095106529307e-01
+2.9012611209933512e+00 9.5281684036681324e-01
+1.9915805925013450e+00 2.1123535546925307e+00
+1.4225951618039208e+00 1.1859398501014526e+00
+6.1589990172442777e-01 2.8963029368524986e+00
+8.4569109833882017e-01 1.0349047879639586e+00
+5.9530478972549465e+00 1.9044217793130156e+00
+3.0681493736051539e+00 1.9007090651587641e+00
+4.7718273267818301e+00 1.0062362900676889e+00
+1.9755111125847642e+00 1.8328610339495572e+00
+1.8439436300516476e-02 2.1461355391824517e+00
+3.8360541816536227e+00 1.4305633008241287e+00
+2.2638846269072799e+00 2.1420459862580481e+00
+4.1649605350174568e+00 2.3317041563901397e+00
+5.2915734030144268e+00 1.7230951157774108e+00
+6.7388978496118668e-01 2.8429442825357247e+00
+3.6677617393353024e+00 1.7138416706530488e+00
+3.2873332247757396e+00 3.0694142357047154e+00
+3.3043681036947192e+00 2.7309391256371711e+00
+2.8876942783078356e+00 2.2159167297521529e+00
+6.0386231838510129e+00 2.2332039292514780e+00
+1.1205120151692043e+00 1.3526447153289942e+00
+3.5029300880468428e+00 2.1198459719230303e+00
+3.9445210531469828e+00 1.4138687044129234e+00
+2.4395144022639803e-01 1.4661794771288905e+00
+4.0737727859255246e+00 1.8925758509367676e+00
+5.2944928977771308e+00 2.0370888380832852e-01
+2.5332219318573692e+00 2.3112634276057067e+00
+2.8348343210280307e+00 8.4612532608148416e-01
+4.9859492644672887e-01 9.8810771812329745e-01
+3.6125456917529508e+00 1.5400538606529239e+00
+5.3809374089601274e+00 2.8750161044441969e+00
+1.2242082613542413e+00 2.1519579233138857e+00
+4.2005946194042743e-01 9.2541033674086171e-02
+1.1262248345172567e+00 1.1754694366423979e+00
+4.3062200068765879e+00 2.1910329125171391e+00
+4.5791954757154043e+00 1.3192689356270266e+00
+4.0240869302465692e+00 2.4579634204322094e+00
+2.9425427177570471e+00 1.4772364815112939e+00
+1.9553863223165895e+00 8.9042203925831220e-01
+4.0859352985774215e+00 1.7203983839841175e+00
+3.5156538756065450e+00 2.4941160235970421e+00
+4.2123054249881484e+00 1.3478112857414246e+00
+8.1333959473882400e-01 1.2873014995996581e+00
+4.2730858090628656e+00 2.4295575499887372e+00
+4.3207375409712103e+00 7.4647605396664307e-01
+5.8209681159426268e+00 1.5926126860434253e+00
+5.2510693388642871e+00 2.0735451115837962e+00
+1.1053049805397559e+00 2.0288313584166171e+00
+5.9680945119891557e+00 1.2314617463715236e+00
+1.2204478624384196e+00 1.5616727058994140e+00
+1.3144029622452331e+00 1.6756869611084313e+00
+7.1145995006879126e-02 1.8888915498224221e+00
+1.8664177235933568e+00 1.4349016782362478e+00
+1.6444682071870882e+00 2.9125156925278723e+00
+3.6617961652199567e+00 1.4380655833307263e+00
+2.5527157342318914e+00 5.1026146582506060e-01
+1.1442969293549652e+00 1.6319119098868988e+00
+2.4785644625089795e+00 1.8818531389092650e+00
+9.4147249707780745e-01 9.7887059421597078e-01
+5.2411333675729344e+00 2.3129882705514819e+00
+9.2304890478933510e-01 2.5488759552603208e+00
+4.2682644175496858e+00 1.6851935847651371e+00
+4.7177124044317633e+00 1.6693677564329616e+00
+1.2123699959425949e-01 1.0428570458532176e+00
+9.2284307234480600e-01 1.2176076863025413e+00
+2.1530967389050959e-01 1.5246664440806743e+00
+5.1054912292800356e+00 2.1846865026876250e+00
+3.1620140328755486e-01 2.2775812711597130e+00
+4.1952784263212530e+00 1.7423027044480131e+00
+2.8753225711398418e+00 1.6886551161049517e+00
+5.0372663365839294e+00 3.3710932247015890e-01
+2.2261944711702819e+00 5.2651943853976779e-01
+1.8666711545875936e-01 1.9623109768987921e+00
+1.4110714583870800e+00 7.1606818721649357e-01
+4.5221156875403645e+00 1.4636592020917472e+00
+3.3989669320347131e+00 1.4858973545110221e+00
+5.4443216280488693e+00 8.6288679212085717e-01
+4.6791828158435411e+00 9.5200998760558664e-01
+6.6552155614714525e-01 1.4701990435359538e+00
+6.0220201485040086e+00 3.0560792303300346e+00
+4.6688098548285186e+00 1.8342632289471230e+00
+6.2977786395062019e-02 1.6652183946984791e+00
+4.2906635483610209e+00 1.2195884743789578e+00
+3.0085444421138625e+00 2.3260467688791784e+00
+2.0390592983062295e-01 1.4642976913841654e+00
+7.1589795590630789e-01 1.7952087155096768e+00
+5.5426577870751137e-01 2.6805856271829214e+00
+3.8536530400925195e+00 2.4134622856052297e+00
+1.4524879727153501e+00 1.8110859653686440e+00
+8.9206995968200697e-01 1.6477788042376231e+00
+3.2810646611989029e+00 1.8842975280606991e+00
+5.8109917581209132e+00 7.8671639510681579e-01
+3.8375435926204897e+00 1.3237171749669507e+00
+4.3928979910458876e+00 2.2721233952913753e+00
+1.4741604430388484e+00 2.3797548843635470e+00
+1.3052105180804106e+00 1.0274006200451518e+00
+2.7024778751839262e-01 1.2836584457583298e+00
+4.2337527256767622e+00 1.9336545582242761e+00
+3.1236083684401525e+00 2.6196316214463824e+00
+2.9992581562563116e+00 1.7145535908483467e+00
+5.4224283371782160e+00 1.6130416154410063e+00
+6.1107809030168720e+00 1.5348752268804307e+00
+4.7854281102234779e+00 8.3900965778115788e-01
+8.8241175824919305e-01 1.0308738196461742e-01
+4.2788642950153273e+00 1.8044863251129943e+00
+4.7833603432791634e+00 2.1973140187294327e+00
+4.0356996723660394e+00 2.0383106073498891e-01
+5.1922508273220931e+00 1.1695822689530186e+00
+5.7740185299439624e+00 5.2566132987221392e-01
+4.0071866988813625e+00 1.2865442705015675e+00
+9.8699027762813252e-02 1.3139310216432891e+00
+1.1511945902472711e+00 1.7956240349391552e+00
+2.3246053846659809e-01 2.8376921463900500e+00
+5.8823893277901265e+00 1.3748077872844111e+00
+2.9894492708272637e+00 1.0750223282646645e+00
+5.0337877124321926e+00 1.2704785022096672e+00
+2.8349432101800969e+00 2.6560355785570606e+00
+3.0755209991240462e+00 2.2964657719694053e+00
+2.6593250730867064e+00 1.0268641880301566e+00
+5.5536976120915709e+00 1.5244236787058838e+00
+5.4740424050659957e+00 1.8079827305407159e+00
+3.9630919884592912e+00 1.9892014927551944e+00
+1.7687910668362501e+00 2.3009121209217755e+00
+1.8509062109670451e+00 4.2698407021726381e-01
+1.5015377010567763e+00 1.7847266926631624e+00
+4.4970907448049928e+00 2.3634451152453981e+00
+4.8840496132815954e+00 1.5161096446506044e+00
+5.1464357569624859e+00 2.8035919606053641e+00
+5.0975339633487513e+00 1.5557736498681622e+00
+1.6081031051578198e+00 1.7881625785168438e+00
+5.0327083871039875e+00 1.0077644675378197e+00
+4.0111091749765411e+00 2.3310912472663330e+00
+1.4478525155385435e+00 1.6375027155006630e+00
+2.4066843381082439e+00 1.3994569446759537e+00
+2.8330590897166115e+00 2.5107506990289998e+00
+3.1091602009510035e+00 1.5614459252618096e+00
+3.3860081150190928e+00 9.9668257526852133e-01
+2.2186696431448629e+00 2.4586583573932907e+00
+1.8931378918578976e+00 1.1507280852274957e+00
+4.3628147561229600e+00 8.4140261779254732e-01
+5.8924370471698211e+00 1.5339221815351256e+00
+3.7094802631399206e+00 2.9946117218500081e+00
+3.7122112755523595e+00 1.7970715776311712e+00
+4.5798949820889305e+00 3.4697228703553185e-01
+2.2825201571973972e+00 2.3909636436038380e+00
+2.8613255131768511e+00 9.9565596421331759e-01
+3.1029784012359469e+00 1.8707472418173718e+00
+5.8617380954440543e+00 2.0368033112473896e+00
+2.8473406253216513e+00 1.5245031869411856e+00
+4.0282948978481548e+00 1.7219554521211984e+00
+2.8613812385059769e+00 5.8814473489530594e-01
+3.4344346715518057e+00 1.8663227497925077e+00
+2.0833835775884735e-01 2.4356936035642471e+00
+5.0512043932516022e+00 2.0795095716070771e+00
+4.2974412135861266e+00 2.3838493384595312e+00
+1.2565644520779191e+00 9.0291442327280214e-01
+5.3722251638531393e-02 1.2959235407102889e+00
+4.8094965345341922e+00 1.2757291370977606e+00
+3.5030312892558095e+00 1.2068151271190459e+00
+4.1417143178717408e+00 1.4973241914225763e+00
+4.0389762032990166e+00 2.0261652060576498e+00
+4.5935803215964164e+00 2.5015963532589778e+00
+5.5742783141924317e+00 8.7822988789840195e-01
+4.2507252926497259e+00 1.9804484515109237e+00
+7.3485891584075613e-01 1.4195711900632180e+00
+4.5268893823824499e+00 1.6243448704504853e+00
+2.8242034541235244e+00 1.2209235232894136e+00
+4.0539935374853204e+00 2.0800308923871373e+00
+4.1699850685219442e+00 2.0610086372844250e+00
+4.2852980622523678e+00 1.1248251962375431e+00
+2.9005437280534485e-01 3.7757381465919071e-01
+1.3987336797293559e+00 2.0370057625851050e+00
+1.0567859847369849e+00 8.0038803148378357e-01
+5.8138160310818687e+00 5.7489089814915728e-01
+3.1850156431235641e+00 8.4014158889311075e-01
+4.2357153389361919e+00 1.0595971038406664e+00
+4.2765467316032808e+00 2.1035850765355431e+00
+3.6298764491365354e-01 4.2058410464278206e-01
+1.4302916044270786e+00 7.7275027547904973e-01
+1.8633781957901694e+00 7.9996078757860822e-01
+5.7102382878227385e+00 1.0132636653241569e+00
+1.3879346065331779e+00 1.6130628285230875e+00
+1.6039476414948945e+00 7.8889863056567466e-01
+4.2730236060187865e+00 5.2616375882256583e-01
+4.2145411293851369e+00 2.8042326461763376e+00
+4.4109265461766762e+00 1.1493334105560051e+00
+5.3095260550219994e+00 1.6715714642916124e+00
+4.5760464402766567e+00 1.4175892540636887e+00
+6.2630563753285617e+00 1.9788538266292477e-01
+3.6120494121830578e+00 8.3256464299507893e-01
+5.7381776229784194e+00 2.9201708672136828e-01
+5.8359785955289478e-01 1.9056290403500302e+00
+3.4406789690826320e+00 1.9968689183276283e+00
+5.8675499280087022e+00 1.9237836975727716e+00
+1.2550772087956992e+00 1.8972270354621821e+00
+3.2229750615338970e+00 1.4354033197911618e+00
+7.3870511202796640e-01 2.0369527558198315e+00
+4.7372380959705982e+00 5.7776109404418652e-01
+2.0098050780039509e+00 1.1958055354762367e+00
+1.2041136278997178e+00 9.1328030930373338e-01
+6.1457596413630036e+00 2.2443114861600728e+00
+4.5501811928193900e+00 2.3342433083129799e+00
+5.1840351025376989e+00 2.6695690478888654e+00
+4.3411587127779843e+00 1.3234898632414254e+00
+4.3804243509692862e+00 1.3661832943893579e+00
+3.2768970948300775e+00 1.8293462437248218e+00
+1.1878409702948434e+00 2.8042585162043370e+00
+2.9813458238650896e+00 2.2263236300260285e+00
+3.4066190572688644e+00 1.5620343233540990e+00
+5.2499896484536386e+00 1.0471338558419363e+00
+1.2787799034269274e+00 1.0750115822577690e+00
+3.5297767401484137e+00 4.6315959925280081e-01
+5.0934728155878393e+00 9.1123750412003113e-01
+2.2710233032528704e+00 1.6776741061541487e+00
+3.4004124016686035e+00 1.9164187231932650e+00
+4.4691853823570744e+00 2.4352940183815210e+00
+1.2962794657032215e+00 1.4763761494324898e+00
+4.0923625394459009e+00 2.2357368244765077e+00
+6.2331418262020550e+00 2.4170329197833915e+00
+3.2472619211691889e+00 1.0091934002254201e+00
+3.5348227549254254e+00 1.6945367423745603e+00
+4.7394254304521155e-02 1.1187105917524389e+00
+5.6312004197847720e+00 2.4354003108509819e+00
+5.2586697640549023e+00 1.6839001273550311e+00
+4.8937851050212924e+00 1.4466268684282253e+00
+5.0549942521605304e+00 7.1849442115729656e-01
+2.3396194267781709e+00 5.9917001125204838e-01
+9.6177314598953778e-01 1.8806783239850446e+00
+2.2815626308916386e+00 1.8729316015966091e+00
+4.1215867651332205e+00 2.5046541681521459e+00
+5.8310539501864413e+00 1.2777881100985056e+00
+5.9462031459814080e-01 2.1730724889148973e+00
+4.7410444568429604e+00 6.6725832453323364e-01
+3.4927316255614027e-01 1.8343824674905331e+00
+4.7861836948950973e+00 1.6970164320206733e+00
+2.9421149312539745e+00 2.1876809478233894e+00
+2.9408508706058933e-01 2.6159631042498543e+00
+5.3151267196957663e+00 9.5538086493550944e-01
+1.0064557173052338e+00 9.4370391437330592e-01
+7.3828946437228682e-01 1.6389603191780540e+00
+1.7894747787016938e+00 7.9328511757608378e-01
+2.0560166136578264e+00 1.3064883005143315e+00
+3.8159227115952588e+00 2.3144878349424198e+00
+4.3615423532497397e+00 2.7308092875240870e+00
+6.1969047149919660e+00 2.3696473679520427e+00
+6.1791423232797662e+00 1.9295380982616386e+00
+4.7315389972845114e+00 1.0358436799754318e+00
+1.7453161835991371e+00 9.9331105789735252e-01
+5.9768238408116803e+00 2.3655145088314513e+00
+2.6205491091783855e+00 9.4269097314284922e-01
+1.3399825209357012e+00 2.2883469095121383e+00
+4.0049158741557713e+00 1.1016557012866586e+00
+6.2591739679631040e+00 2.5486876554665701e+00
+2.3993253300981809e-01 1.7856022840335888e+00
+1.1493172826060425e+00 8.6853325955608118e-01
+1.2196288992324320e+00 1.5069780276059852e+00
+4.5130587278931564e+00 4.3936128180361433e-01
+2.8069771020735685e+00 1.9626812313749586e+00
+5.2825401153388523e+00 1.3977018551555862e+00
+2.5358030863891949e+00 2.2156633494483242e+00
+5.2584786717031875e+00 1.7586456518883311e+00
+4.8080228284760009e+00 6.1334358942360279e-02
+3.6652505123590995e+00 1.8250055052409477e+00
+1.0491038362446092e+00 5.5304747170841706e-01
+3.3262263258603806e+00 1.1439825222619437e+00
+2.7949907395357623e+00 2.8065967714705295e+00
+2.3823071056599292e+00 1.5872560942728395e+00
+5.8026554216721937e-01 1.2105868638684250e+00
+1.6678281855244854e+00 1.7431863029455203e+00
+2.1303308914068646e+00 2.3451696927344581e+00
+2.3649523606467819e+00 1.4963192676013530e+00
+9.0115126913928512e-01 4.1131226256512687e-01
+2.0031036445601664e+00 3.4327268704986991e-01
+1.6719003697931456e+00 1.7897988391615383e-01
+5.2398399956379151e+00 8.2830388504728758e-01
+2.1918844192618909e+00 1.2353711168946504e+00
+5.4224607439808450e+00 7.5033988759348291e-01
+1.7289513765314899e+00 7.7213045301715333e-01
+4.8829290641792618e+00 1.0198561250910363e+00
+3.8976446341755859e+00 2.3231210817541443e+00
+1.1008866207232648e-01 2.5262532826401887e+00
+4.4600854266634293e+00 1.5460579061996702e+00
+1.3670648142409902e-01 2.4725948229519563e+00
+4.3168705101750842e+00 1.7121162540205168e+00
+5.4681175329048859e+00 1.9188811104237411e+00
+2.7410161482645465e+00 1.8101398075791286e+00
+4.0145732952466533e+00 9.8548903262984933e-02
+3.6650253308276035e+00 6.8253284838654416e-01
+1.3856290897853045e+00 4.7090411520639286e-01
+4.8859012587002333e+00 2.0665138394426306e+00
+1.7670664015567346e+00 2.6498449990498081e+00
+5.8134156966539878e+00 8.7302243115220146e-01
+1.9418385872387711e+00 1.1763459358788233e+00
+3.3117410863259948e+00 1.4043326708578083e+00
+2.1541866635150209e+00 2.1740532975699938e+00
+1.4212529744842104e+00 1.5130517911027432e+00
+3.4294746151929569e+00 2.6074293216974835e+00
+6.8106610876174756e-01 1.6818435350615402e+00
+8.1909157044991865e-01 2.2232535131487037e+00
+6.0124458187913143e-01 1.9460830718765862e+00
+1.9198031577956975e+00 4.6954981033131493e-01
+5.1164947209154326e+00 7.4744931453630958e-01
+3.1585344703984775e+00 2.0517663544040334e+00
+5.4424172036212788e+00 1.7652779699206289e+00
+6.1568800005698767e+00 1.0969660166624242e+00
+2.0674269811519435e+00 9.5085917651104512e-01
+1.3535803648099662e+00 2.9163306720444604e+00
+3.1780852255855105e+00 2.2112261306971526e+00
+2.2506781603441328e+00 2.6086521153000484e+00
+5.3573534209654117e+00 1.1650972493553882e+00
+1.6901612119980183e+00 7.2654474073857278e-01
+5.3418713182437774e+00 1.9977756442251104e+00
+3.6721755901664310e+00 1.1463822088991964e+00
+4.6064836004200433e+00 2.3273904440726505e+00
+2.2625224929355023e+00 1.8225210149846873e+00
+6.3660148566706098e-02 7.7376278980608759e-01
+1.1810237744900365e+00 1.1758649966012857e+00
+3.1378163808515205e+00 1.1469360354496969e+00
+1.9515187323808325e-01 7.9465966569976509e-01
+3.4032952073595686e+00 1.9682880200702790e+00
+1.7715610617526503e+00 1.2305001089528456e+00
+4.4429749491984438e+00 2.0372092635314778e+00
+5.5282703761408261e+00 4.2216508365527083e-01
+1.6088083207083903e-01 9.5051700702861353e-01
+2.2084920824578846e+00 8.6311676166935092e-01
+2.6275917988042190e+00 1.0709204921743822e+00
+4.2100009571014674e-01 8.7553350453940337e-01
+5.0903881708248777e-01 1.7911523800644038e+00
+4.8767092288117553e-01 2.3801672913696335e+00
+5.5606489806867376e+00 2.3383293564945196e+00
+6.1125959906543841e+00 8.6931951748101888e-01
+2.4587761981747387e+00 2.7565310050157636e+00
+1.9857391389026340e+00 1.4454224449644648e+00
+3.4861229479522997e+00 8.7570999630000146e-01
+5.0169087710014688e+00 4.5582055334544846e-01
+5.0462146900843408e+00 1.6966576089237360e+00
+5.4903688838516551e+00 2.6082863560668947e+00
+5.0143467799948889e+00 1.5671808709669373e+00
+4.2189308910136614e-02 2.1926645312786901e+00
+5.7634875078389705e+00 1.9493810393942885e+00
+6.2784752382900288e+00 2.5937743015421599e-01
+1.7763567858895168e-01 2.2782221992428062e+00
+4.4743105473484723e+00 9.5905026774103208e-01
+3.3476502458690605e+00 1.8903608658269575e+00
+3.2576877387693490e+00 1.6195418962391734e+00
+3.1866544476536443e+00 2.4995688244309093e+00
+4.5204372472899879e-01 2.6630703149618244e+00
+1.4901137323449265e+00 7.4791956837976437e-01
+7.3347092984170503e-01 2.5936744428482315e+00
+6.2355595082981345e+00 7.1320318679765327e-01
+4.5142629841339943e-01 1.7611854558330613e+00
+3.6750218792443823e-01 1.6852076394766116e+00
+4.1921931275111275e+00 1.9118150094492044e+00
+2.1937831334103683e+00 2.7834811840524498e+00
+8.2397644828572503e-01 1.0983168459018344e+00
+4.0261449040141901e+00 1.8630637734754323e+00
+4.6912110636362225e+00 1.3569463848866818e+00
+5.1002894442497650e+00 9.9461404686377741e-01
+1.7472073766968583e+00 1.1507419722182060e+00
+2.7200725365903198e+00 1.5761393868811771e+00
+2.5578007639782978e+00 8.2397893850057946e-01
+3.8254308233090724e+00 2.5704002197735050e+00
+4.9697012213926239e+00 2.7908144541635709e+00
+5.9509944558646568e+00 1.7001272940332921e+00
+8.9574505031566121e-01 2.3644624922847246e+00
+1.5779587017652221e+00 8.6504046343173302e-01
+1.9315286132972549e+00 3.0516482311107231e+00
+6.1966273531821319e+00 2.4687430718367276e+00
+2.1818200275419395e+00 1.7801812634458858e+00
+5.6768382342958237e+00 2.1179052406870387e+00
+1.5239132408446101e+00 3.7616681646860917e-01
+1.3427760541756926e+00 1.8587757028414698e+00
+6.2103817957856275e+00 1.5660144402282288e+00
+5.9665548243101618e+00 2.0585094276180196e+00
+4.0763900273800218e+00 1.4095716487480714e+00
+3.1039848144021041e+00 1.5138849017722467e+00
+2.1431648749073013e+00 2.8623895364203267e-01
+4.8649157352808654e+00 1.2871949318976985e+00
+5.0022490301796889e+00 7.4450396244114314e-01
+2.1144932519308135e-01 2.4844976769985228e+00
+3.0701815966398405e-01 1.7229758857768360e+00
+2.0532819577286019e+00 2.7241734282195580e+00
+5.0640921355499033e+00 1.3865727049636871e+00
+3.9699357529476718e+00 1.0218817487119576e+00
+6.1463737851132692e+00 1.2174358845021225e+00
+1.0655872470359373e+00 2.6714832601337726e+00
+3.8846721122218000e+00 1.1779191341826334e+00
+2.0108988107036585e+00 1.9867965212376362e+00
+5.6998059765986442e-01 1.8611526151080384e+00
+5.4246239840587984e+00 1.0971485139643145e+00
+3.6951445991003116e+00 2.4705304356983389e+00
+1.8616879071964663e+00 2.1347544184252092e+00
+1.0588700090252656e+00 1.7240952671833907e+00
+2.1663204351707646e+00 1.3637812105348004e+00
+4.3195040369894633e-01 1.9664032075267928e+00
+4.1222628071931444e+00 1.6847536251779944e+00
+1.4835705025918418e+00 1.9994166913640465e+00
+4.8475126958263974e+00 5.7147072519168196e-01
+1.9333515582746430e+00 1.2724425501043832e+00
+2.5864123436723410e+00 2.2500116950699982e+00
+3.5192347427325021e-01 1.2708350157479291e+00
+9.7635592425098228e-02 2.7509487578331937e+00
+1.1756338260211057e+00 9.9149150930785801e-01
+1.5416628768741758e+00 3.0012076851630294e+00
+3.9465206554750765e-01 1.8087686454681196e+00
+2.1195351746712019e+00 1.1649253458616089e+00
+2.3314079957830058e-01 2.7356157469802569e+00
+6.5486083224368707e-01 1.7184697338859665e+00
+4.5796388348857509e-01 2.2376022284347084e+00
+5.6668899263005397e+00 1.4392476300119936e+00
+1.9976092251800852e+00 2.2787989290029413e+00
+3.6777028661480973e+00 1.7672471570606876e+00
+2.4100151344015877e+00 7.0104096568449914e-01
+5.7866676842706726e+00 3.0190993256423790e+00
+4.6683903553149584e+00 1.2141609850093820e+00
+5.5240886815391859e+00 2.0153785808823712e+00
+3.7258175048074977e+00 8.7633363823561661e-01
+4.3828136217044147e+00 2.0466466584728025e+00
+2.5069238218793721e+00 1.7882775201992176e+00
+1.2994106156599095e+00 4.6177691952945810e-01
+7.8194552515362004e-01 8.5766320683385011e-01
+1.1390538686594578e+00 6.7929983863696053e-01
+5.8612529472446919e+00 5.1134406395735077e-01
+2.2376074629777376e-01 2.3847632842949298e+00
+5.4840033822560521e+00 1.8634651939018658e+00
+3.2524091475378789e+00 1.0528739676873493e+00
+5.1364668019560789e+00 1.3019072013229176e+00
+2.8622895469312990e+00 8.9434578013445076e-01
+2.2042488220240140e+00 2.1378402277160911e+00
+4.7556721844177146e+00 1.8848331421442550e+00
+2.2767944307658494e+00 2.1885854650499774e+00
+3.5138541323162134e+00 2.8157172598393840e+00
+5.5290148563612433e+00 1.2282353842893774e+00
+5.2903540423860136e+00 1.9961478912980208e+00
+1.4782772489566323e+00 2.7460787267501168e+00
+2.7201078393122842e+00 1.9914713825711634e+00
+5.2230021235933304e+00 2.4131706490452003e+00
+1.0304463962883776e+00 2.6024591219694013e+00
+4.7443012864127034e+00 1.4121247638931609e+00
+5.4939848567290710e+00 2.9064104734426683e+00
+3.1520272158738272e+00 8.0070979863257286e-01
+3.6710349773215847e+00 2.6123439592224575e+00
+5.3839163748664838e+00 1.5910838647244983e+00
+6.1993237824814500e+00 2.3108299730019595e+00
+4.9613272046586907e+00 1.7259786388297311e+00
+2.1897704303534242e+00 7.4999966653521621e-01
+3.1032210288417907e+00 1.2656747584700716e+00
+1.6060272735367116e+00 4.0225515977464532e-01
+3.1010994207702662e+00 1.7684908598321474e+00
+5.0293095776026409e-01 1.7457261498188437e+00
+3.1308699739446855e-01 9.4663158502572142e-01
+3.9993232638604521e+00 5.8019683055389593e-01
+3.8973422979000727e+00 1.2426941345080196e+00
+5.2734708393073060e-01 2.2624826499015711e+00
+3.8218443388396732e+00 1.9253354679930004e+00
+3.3860427179241448e+00 2.0430704916917364e+00
+5.3891773394384810e+00 2.1324101997433815e+00
+4.0528582388104848e+00 1.1242769478234025e+00
+1.6930366745544523e+00 1.9608959127774872e+00
+6.6140760317545277e-01 2.8869235805435967e-01
+3.1952342468041861e-01 7.5510242831372010e-01
+3.9230078119970222e+00 1.2974020251314067e+00
+4.3703553944431670e+00 6.8411888424138723e-01
+1.7417911915836868e+00 1.9405069198762157e+00
+2.6591499016651037e+00 1.3638257316848690e+00
+2.3143310551947214e+00 1.3010893529816645e+00
+5.3370398238606231e-01 2.0180746771385811e+00
+4.4667655254419358e-01 1.3869101794873000e+00
+7.1750473278374027e-01 2.4592187547847093e+00
+4.2549311603899440e+00 8.7221107428306166e-01
+1.3782823075598023e+00 6.5940396503311760e-01
+1.4864558639559335e+00 1.2652720060665832e+00
+1.8388226665889018e-02 2.6638697024682196e+00
+4.4903173649210277e+00 4.9373723348294130e-01
+3.5322466193437201e+00 2.6520016004793767e+00
+3.4604568424524098e+00 2.0914124506596354e+00
+4.7276425456661819e+00 1.7947625952717221e+00
+5.1822637737749577e+00 1.7423928864061551e+00
+2.5232684063056485e+00 2.2660317416925366e+00
+8.5637215819509560e-01 4.6555433478706898e-01
+1.2956192808656888e+00 2.4739896994507968e+00
+4.5168703955822576e+00 1.0402216421951420e+00
+4.2595476165688355e+00 1.2518495957025881e+00
+3.6011212533559966e-01 1.9714384953311685e+00
+2.3991680875598522e+00 2.4380309206456485e+00
+3.0334912481561158e+00 4.2934239300857602e-01
+4.9087053469299864e+00 8.7937437009130215e-01
+9.6542027166034705e-01 2.3110639032904268e+00
+2.9672611250759657e+00 1.7805147402003259e+00
+4.2094966018187652e-01 1.5802750569050863e+00
+4.9287638868448251e+00 1.0449138699198532e+00
+2.5028024276136893e+00 6.8648025909692967e-01
+1.4589874012435815e+00 2.7057418135589151e-01
+4.9556796313038465e-01 1.0926079530928630e+00
+4.6813315702247449e-01 1.3356068645840886e+00
+5.7823097440558540e+00 8.3267971961373877e-01
+1.2925611306438201e+00 1.8361285960497544e+00
+5.9983072800399491e+00 5.9616810762105465e-01
+2.2191493306845196e+00 9.7083443415197390e-01
+6.0650379377227104e+00 5.6568782911778004e-01
+2.2467597221163222e+00 1.7307008460620392e+00
+3.3759033620366452e-01 2.5662442524402778e+00
+3.8024852695627240e+00 5.6033904974858761e-01
+3.4609527907729922e+00 1.3493706114203261e+00
+5.6499569088763995e+00 1.0859616320618843e+00
+1.2606301916934728e+00 1.4154526284455746e+00
+4.8631899972074519e+00 1.0998728108685230e-01
+2.2747391688195400e+00 2.2760714936277671e+00
+6.1116603905178257e+00 1.1808364291238453e+00
+5.3163510945898462e+00 2.2951261297308618e+00
+4.5316545053260100e+00 2.5344854626688296e+00
+1.3577406493568749e+00 1.2981977081421734e+00
+2.8549080127310056e-02 1.4673108329848865e+00
+1.3775196196539130e+00 9.9249101047345278e-01
+3.3011427292876570e+00 2.6335896095938272e+00
+7.5968782260976297e-01 2.7267072894613507e+00
+5.3174705679804557e+00 1.4301930597228274e+00
+5.7605047252611694e+00 1.0222261427315309e+00
+2.0831631357206457e+00 2.2971978598061571e+00
+2.9335442438360473e+00 1.5220311645120119e+00
+6.2648873724886895e+00 1.4447229217287698e+00
+1.9792237800503263e+00 1.3363840194816046e+00
+3.0682678444996538e+00 2.7089744517342833e+00
+1.6396938914227359e+00 2.0615916486969796e+00
+4.3474663777104077e+00 2.2213083859623342e+00
+5.7214128495674270e+00 1.5252977280779478e+00
+3.5341116699916748e+00 1.0448347276016752e+00
+1.6802765705327347e+00 6.1148080375546154e-01
+5.7574790389172783e+00 2.8356906775862813e+00
+3.5757747258265216e-01 8.5323757336607142e-01
+3.9509979067607133e+00 8.1642306320080693e-01
+5.9777360164761522e+00 1.6328530972807060e+00
+3.1984867876260568e+00 1.8431063398566834e+00
+6.0947148590375404e+00 2.6115416599086543e+00
+1.6463885868304760e+00 2.2835926136829068e+00
+3.2867655405016936e+00 1.1872766351017146e+00
+5.6814432945822668e+00 1.3483769180551211e+00
+1.3105436122793834e+00 1.2525023526881680e+00
+5.1408845006716541e+00 1.6847346963153218e+00
+8.8457294666696518e-01 9.6097787049053263e-01
+2.7450726812370032e+00 1.6441303446023852e+00
+5.4016611490164159e+00 6.2810596048174794e-01
+4.4264190360977755e+00 2.6299587059197762e+00
+3.0612366716848016e+00 1.2445473908776918e+00
+3.8390472651863843e+00 8.6580113137723558e-01
+4.8603167370673139e-01 1.9900169902485798e+00
+1.5311123527769153e+00 1.5345829770144794e+00
+2.0225824883844989e-01 7.4102257276089611e-01
+5.3265390333677800e+00 1.4995278738281741e+00
+1.3362285115783878e-01 9.1366421356308725e-01
+3.4292615490543175e+00 1.3881690261151827e+00
+6.7499462712241698e-01 6.7858967109825419e-01
+3.6472398133205606e+00 1.6686223023897451e+00
+3.9276809925266498e+00 2.5235495975242075e+00
+4.7326226258639235e+00 5.3433911143607737e-01
+1.9993672094846877e+00 1.7708028093036197e+00
+5.7530569849557729e+00 1.4861205053763518e+00
+4.9772017673829190e+00 1.1806413942975396e+00
+6.6988692388537707e-01 2.6540684671409687e+00
+1.7594126045506580e+00 1.8992150087981585e+00
+5.6473188906478171e+00 9.3705192390002101e-01
+8.7823269949991811e-01 2.9316963578540003e+00
+5.0614336372706923e+00 2.6606008364389728e+00
+6.3926673934681144e-01 2.0604193840917886e+00
+4.8567917573594466e+00 1.8816477621484207e+00
+4.1759859506087658e+00 9.0395861551951073e-01
+4.9168038723230589e+00 2.1927066664171493e+00
+5.8475672364008293e+00 1.7246632224285685e+00
+1.0657748189512146e+00 1.1863036114681791e+00
+1.2353640731174043e-01 8.1682010624574064e-01
+5.2056715287397282e+00 1.5070265204009128e+00
+2.9386539317795188e+00 6.1144791191946568e-01
+6.9712353018701023e-01 5.6131168263184583e-01
+5.3503598379808937e-01 1.3649568884429688e+00
+7.9473410668899724e-01 5.7110109732725300e-01
+1.0937810155083891e+00 6.3611912495837108e-01
+6.1066442910131613e+00 1.8773814430637272e+00
+1.3734945499144453e+00 7.6579522557236102e-02
+3.7227367549880253e+00 6.2811689295240103e-01
+5.3770835762957265e+00 1.8377604874941622e+00
+1.5593083346847589e+00 9.2939517976764818e-01
+4.6665382881814770e+00 1.9038085179132405e+00
+2.5955639115562681e+00 2.3041530168400706e+00
+1.9896664185922730e+00 1.0530011656503593e+00
+4.1125670743040459e+00 1.2742915465988753e+00
+2.4809714953578919e+00 1.3811244724467291e+00
+7.3833157130321725e-01 2.1069598561418807e+00
+6.1198895321098270e+00 2.0000932037882846e+00
+3.2111614500081802e+00 9.7245403032571676e-01
+3.1778129786643787e+00 1.4131467532090081e+00
+2.1042722603989006e+00 1.9761141111864995e+00
+1.1768081859740076e+00 2.0439117910685169e+00
+5.4304497159299165e+00 2.1561801061270911e+00
+1.6863377373217485e+00 1.5465858144858129e+00
+3.3319615890013705e+00 1.4457722024932502e+00
+3.4883613648590579e+00 2.5746724607147708e+00
+6.0303035588089013e+00 2.3356942525054167e+00
+1.7175798771278608e+00 1.5921152433427332e+00
+1.6931818511381251e+00 1.5039626120284875e+00
+1.9443244598702472e+00 1.5852935776008581e+00
+3.5103763909786019e+00 2.4397825195560650e+00
+2.3958894519461711e+00 2.3527778869497809e+00
+1.4988068080587997e+00 1.7370518269707698e+00
+4.2049147377793821e+00 1.1546517852603355e+00
+1.6939973191715729e+00 9.6533917586513707e-01
+6.0487794893071829e+00 1.3151813885286359e+00
+3.0468912555309400e+00 1.4243584668477356e+00
+2.7959481448822201e+00 3.3201331229067987e-01
+3.5272882397771150e+00 1.5049252602804823e+00
+3.8634436242074739e+00 2.0099694251698423e+00
+5.0843859604652613e+00 2.2359223419270475e+00
+5.5030579616299278e+00 2.1603928941226336e+00
+9.7742946934941699e-01 1.7419474424648076e+00
+5.4812192173201124e-01 1.4302666109989877e+00
+1.9430383470326440e+00 5.8560833758737718e-01
+3.8368495907138760e+00 1.9638536704092089e+00
+2.4337152287993407e+00 2.0435579426759323e+00
+5.8156482369093991e+00 1.3542670344668117e+00
+3.3713570090396372e-01 2.1662609547138607e+00
+6.2593824093923480e+00 2.2508580119234565e+00
+4.2976793106762248e+00 2.8957647370295136e+00
+3.8534520412606930e+00 1.5145051601525439e+00
+1.2915817280017146e+00 3.0326687588840739e+00
+1.5024308173525958e-01 1.4419453334865957e+00
+3.5789493783327191e+00 1.4011087915251468e+00
+1.3845823521710443e-01 2.5835201407090516e+00
+4.5279277354127139e+00 1.7720345600499186e+00
+1.3686384620412555e+00 9.3985916852674123e-01
+1.5593230671123164e+00 1.4351303340204211e+00
+6.0187581227764912e+00 1.7267524019348777e+00
+1.0952387169583755e+00 1.5630228913171169e+00
+5.0582370812290183e+00 1.8029908403938326e+00
+2.0902401733454168e-01 2.6045282941204571e+00
+1.9574214407474757e+00 9.3819295703218231e-01
+6.0478151836236824e+00 1.0203702010566027e+00
+1.6928771234421160e+00 1.4206945653833880e+00
+7.8102852474110307e-01 1.3252868466176904e+00
+3.8038992500995619e+00 1.3664572792275804e+00
+4.0563868054805408e+00 1.3006866766988363e+00
+5.4411123629400544e+00 2.0904469917798663e+00
+5.5852812788088571e+00 9.6202430489756263e-01
+3.3524760587749602e-01 1.1697249170757673e+00
+4.8227113433701465e+00 1.0088856214898385e+00
+5.3732534501963007e+00 2.3689625816674269e+00
+2.4687506666868568e+00 3.0471428901620974e+00
+4.3303899860402728e-01 1.0631870940797652e+00
+5.3606719732942754e-01 8.9622280750707606e-01
+1.2530119908538124e+00 1.4646961460249857e+00
+4.7579951985012334e+00 2.6244735665812842e+00
+2.5696338505470337e+00 1.9151561974175559e+00
+5.1738683622792783e+00 5.3431895218370218e-01
+9.2480825469989814e-01 7.8382351406452500e-01
+4.6086036523726834e+00 1.2045138579429429e+00
+9.3822888622414968e-01 2.2564941308334840e+00
+5.2778516112085025e+00 1.1690989435897512e+00
+3.4552554499974706e+00 2.5345123222576085e+00
+6.0688507398192488e+00 1.5070498253035807e+00
+4.8852136071690211e-01 1.6106714038875609e+00
+5.8260079425613105e+00 1.2216550723184170e+00
+2.3413797377985981e+00 1.7176996357123915e+00
+4.6887188187190176e-01 2.7827806390241552e+00
+6.0513383586151912e+00 1.6838481709749038e+00
+4.0860448386244244e+00 1.6439667558064532e+00
+2.7776587896952196e+00 2.9667271677652085e+00
+3.7129518015716378e+00 1.3510565674366375e+00
+2.2286392052454596e+00 1.9623008389547625e+00
+1.3776709517869261e+00 8.3343257916831548e-01
+2.1694572138412038e-01 5.9676103351077847e-01
+9.5735214665729540e-01 2.0646762573560888e+00
+2.4159311090988851e+00 1.9932147985546242e+00
+5.5290605689883243e+00 2.5220358888327850e+00
+2.7948069085582019e+00 1.0972167221524949e+00
+4.0380540980364454e+00 1.0578301544027111e+00
+2.5044132162505148e+00 1.3289805845910974e+00
+5.2364294835695100e+00 1.8215461934605046e+00
+4.7705913300657876e+00 1.3038314156939292e+00
+7.7592744180208406e-01 1.5341772980323551e+00
+5.4741918587653204e-01 1.2594936998462574e+00
+4.1258289563756829e+00 7.1699758917413925e-01
+2.4079384801874659e+00 1.9217365896635650e+00
+7.0014993654530233e-01 1.5050429394871681e+00
+3.9493126281427404e+00 2.7754934383632199e+00
+3.9051156616655431e+00 2.1965185408831349e+00
+5.7446964289034330e+00 9.5852003422927867e-01
+3.7369203997151050e+00 2.0252498823354483e+00
+3.0314325084759508e+00 2.6573785437300312e+00
+3.5614540626390530e+00 1.1448440133453139e+00
+6.1567774180931103e+00 1.3456094618180865e+00
+3.2248065490825044e+00 1.2689703366673883e+00
+2.1451092313055300e+00 7.1999823994935985e-01
+6.1383368365023454e+00 2.3550281715366057e+00
+6.2248208685000570e+00 1.2676289139566936e+00
+5.6023799929257212e+00 1.8169601181464969e+00
+4.6503056881978413e+00 1.1670034206469615e+00
+4.2865855847395968e+00 2.0251035950152958e+00
+4.6150312755525755e+00 1.2944258797969734e+00
+2.7067043356498455e+00 1.8416190491949265e+00
+2.5423495108726444e+00 6.4877260788005664e-01
+1.3062860954466451e+00 1.7936694893066931e+00
+5.4885654531563421e+00 1.1949053044936617e+00
+5.9300886056739657e+00 1.0632206903334640e+00
+5.9451217711541666e+00 2.2109374437351863e+00
+6.6327064549346115e-01 1.2014835881220096e+00
+3.5486391295665287e-01 8.9967467888507668e-01
+9.8954636250429573e-01 1.1345844961327876e+00
+3.2407948990864277e+00 2.3172999727239159e+00
+4.8635850010065873e-01 2.5064086660908744e+00
+1.1352378704874466e-01 8.7410777909500670e-01
+5.0437815182966572e-01 2.9756292562241482e-01
+3.8189422104234176e+00 1.0199683487038147e+00
+2.8317771733885611e-01 5.6160669052866141e-01
+4.1482610917507499e+00 1.3355453249248521e+00
+3.5982185743282242e+00 1.6863652815707522e+00
+5.1899828229604550e-01 2.7296843128902553e+00
+1.5279987788183613e+00 1.3837816071313878e+00
+2.2762178463271190e+00 9.8170829347280741e-01
+7.7619663126089611e-01 9.2469094292496290e-01
+4.6210085155854062e+00 2.1594916688679344e+00
+2.8753715197997818e+00 1.0495754032232680e+00
+9.6509958083198732e-02 1.2027466650156478e+00
+4.3426217895295363e+00 3.2519200416775140e-01
+5.5548581756873467e+00 1.7601430222934660e+00
+9.0678638260057243e-01 2.6732943199484636e+00
+5.0993240861551978e+00 1.9045279177432679e+00
+1.5879943337978402e+00 1.9619311670912838e+00
+1.1667932409126403e-01 1.5854944270088478e+00
+3.2189611301676857e+00 2.6175096534616999e+00
+5.8406315862919307e+00 2.5398333229479966e+00
+4.8332883690443182e+00 2.0742419058403572e+00
+2.5566162213362054e+00 9.5872225123412225e-01
+4.5415359189672131e+00 7.0724433278866794e-01
+3.0152705053194495e+00 1.3140000041618987e+00
+2.7514597783334276e+00 1.8881747938652884e+00
+5.9196634568557185e+00 2.0421557137254491e+00
+3.6661840182779573e+00 2.7874377125804135e+00
+4.9549144088923027e+00 6.9927308124159493e-01
+4.4981209442816263e+00 2.0857430577733553e+00
+1.1523180301251066e+00 2.3509408193420978e-01
+4.4339529167248859e+00 2.3961393678338609e+00
+2.5325701337778073e+00 1.5655492486957960e+00
+8.0277692764155617e-01 1.7892540211358128e+00
+4.0559420864150493e+00 9.1896728591858901e-01
+3.1674723147013233e+00 5.4644648580175015e-01
+4.0479559345519878e-01 1.8941769795823651e+00
+3.3556366089621616e+00 6.2122808280197683e-01
+2.0464440963109496e+00 2.4562864846112231e+00
+5.3772727596052530e+00 2.8174431099365806e+00
+6.2817694944590468e+00 6.0464798876965797e-01
+1.3238405857509132e+00 5.3635574262941521e-01
+5.5176550180453994e+00 1.3841051069856889e+00
+1.3970712374351533e+00 2.6406818371432941e+00
+2.0646371361017328e+00 7.1357593512894812e-01
+2.0993757743520045e+00 8.3482321143903737e-01
+5.0654647887414859e+00 1.4889536670989900e+00
+3.5672870979643925e+00 1.6496389961919466e+00
+5.0195659445596634e+00 2.6107885073400072e+00
+5.9133189520649765e-01 1.8211896145723043e+00
+1.2341559275986207e+00 1.0891952714834647e+00
+3.9918016199300257e+00 1.5052370434530904e+00
+3.6628848747385767e-01 1.5342464853939275e+00
+4.6782216217722237e+00 8.0232003102054006e-01
+3.9312015628122525e+00 2.8864378117352008e+00
+5.0116841123720759e+00 2.0403015308304573e+00
+1.0571759567289005e+00 2.3555625783991823e+00
+4.5673972173919939e+00 2.7334352956455858e+00
+4.7392116539089013e+00 1.9865682195612340e+00
+5.4590115075302919e+00 6.6723720610842230e-01
+2.7190770578324575e+00 2.3061482872758137e+00
+7.6936997792383677e-01 1.7017795007045544e-01
+1.9828458770344637e+00 2.4520888309167610e+00
+2.0993936179412702e+00 1.6233997463108847e+00
+2.7970566304650495e+00 1.7974840476664824e+00
+2.2768025020132243e+00 1.2616402871388690e+00
+3.8745259790491788e+00 6.9973933199921090e-01
+5.8724897429140057e+00 1.9823222823770732e+00
+1.8149733427773433e+00 2.0344255664242632e+00
+1.5753661245395207e+00 2.5303902401551053e+00
+5.8025937065713471e+00 2.2569079948810860e+00
+5.7911531219460732e+00 1.0585813784985159e+00
+1.9478509213423301e+00 6.4388274708765503e-01
+4.9609061914522457e-01 1.2732920550620734e+00
+4.9419414883257549e+00 1.5600900019867241e+00
+8.6618677233363650e-01 1.1462087562924015e+00
+2.8737653206123772e+00 2.7114224470789061e+00
+2.7246233616864814e+00 7.1021979559943560e-01
+3.7586123523591399e+00 1.1946081773955861e+00
+8.4686627622054633e-01 2.2697583250191382e+00
+3.7380141812983778e+00 1.5771892643490046e+00
+8.9603542169652739e-01 1.4589299092421419e+00
+7.0906006784537823e-01 1.3010959818347951e+00
+2.8771991878790244e+00 3.7231517166034878e-01
+1.7241286241858069e+00 1.4615257282042879e+00
+5.9499793714856368e+00 1.4217100062518946e+00
+3.2685946768690419e+00 4.5486480546228480e-01
+2.7373552012194677e+00 2.6723177291713802e+00
+1.9811721400421018e+00 1.2843620410895071e+00
+2.9342945254659729e+00 2.4544973422925267e+00
+4.9085847660547746e+00 1.3686951236455989e+00
+1.0739255670507366e+00 9.3074556359748262e-01
+1.0198518599382345e-01 2.7986021710301889e+00
+4.5097065460409436e+00 1.5695464182966923e+00
+3.7676101244179994e+00 1.9734916205002051e+00
+2.6584464260524547e+00 1.2523151377462678e+00
+5.7598941314399008e+00 1.3387314594799899e+00
+3.0088366792189229e+00 2.6102650334052409e+00
+5.7215483884277045e+00 6.5574919924084940e-01
+3.2001108802690945e+00 4.2327439128724165e-01
+5.8380162496140162e+00 2.2892221889074591e+00
+4.4021001765815875e+00 2.5775003926299060e+00
+4.3792862227329064e+00 2.5962693523358782e-01
+6.2464455417711076e+00 1.6996914878176677e+00
+3.3345568791474789e+00 1.0154269212729727e+00
+2.6486876751783353e+00 5.9221634785988009e-01
+3.9723795967633668e+00 7.0919044994208535e-01
+3.5860976285393491e+00 3.3333945590177594e-01
+1.3386783156496260e+00 2.0073221605020399e+00
+3.4710019923632682e+00 1.1049039571269668e+00
+5.3019088869762134e+00 6.2030875588753298e-01
+4.8327895037669766e-02 7.0431998171771215e-01
+7.3205322506051385e-01 8.7796952252556959e-01
+4.8274566552193150e+00 2.7492253041540202e+00
+9.4213830272336541e-02 5.9888479709732778e-01
+9.1211977849284498e-01 1.3170767925278108e+00
+2.0220415118807300e+00 1.3886707593618093e+00
+5.6306116589159148e+00 1.1517290728983554e+00
+3.8335793855880005e+00 1.5733459805762917e+00
+1.3356747320424445e-01 7.6418264596378815e-01
+1.9315385593798018e+00 2.4083324870071983e+00
+1.1898651564358311e+00 1.2373877894621210e+00
+1.6955334184549056e+00 1.3720463362854314e+00
+1.4301826811873486e-01 1.5432767225579807e+00
+5.2170645843041683e+00 2.1755110547484566e+00
+4.8425497387557073e+00 3.2684097901018183e-01
+4.6119782952854038e+00 5.5858251776700185e-01
+5.2134317379448945e+00 1.2262297450159723e+00
+4.7805890436780105e+00 2.4366687116815351e+00
+7.3106399192867766e-01 1.5856505970789154e+00
+2.4686322349656491e+00 1.6527181516632776e+00
+6.2756442670074941e+00 2.6077303349266598e+00
+6.2518005699840797e+00 1.7643877222307411e+00
+5.6390126605320390e-02 9.2921158422181138e-01
+1.0631004789525571e+00 9.7708167438265703e-01
+9.6416986961307494e-01 2.4256434926442378e+00
+3.2191538350998004e+00 1.7440854739855922e+00
+2.6752674622368069e+00 9.1287490379844427e-01
+3.6452470709152660e+00 2.0787033346386807e+00
+1.7431719462224411e+00 1.4065055631593639e+00
+3.8503108336518070e+00 2.7620113072064809e+00
+3.6537445160609328e+00 1.1941659917575642e+00
+6.7325374313133590e-01 9.1827617608359802e-01
+4.7012643820652800e-01 1.1905001310651102e+00
+1.7267728341477007e-01 1.8922873142403838e+00
+1.0601681040307334e+00 1.5935848935719981e+00
+3.2391755801599675e+00 1.9959648881160701e+00
+5.2961552476087093e+00 2.0479341208016746e+00
+1.9629786223540122e+00 1.0020614686122802e+00
+1.2028195929905567e+00 1.4034466027659309e+00
+4.0449701209556199e-01 1.4608662808869710e+00
+6.0578696911810752e+00 2.2348182621071078e-01
+1.5112059993749707e+00 9.5960556419403731e-01
+3.9356454927744351e+00 1.3531248769561317e+00
+1.3121746004091255e+00 9.1939788332459838e-01
+1.5088395946726465e+00 1.4358289115291540e+00
+4.6515475649631473e+00 2.5994719122190695e+00
+9.7241208794732259e-01 1.5800010207203252e+00
+5.1899111921216070e+00 7.5406091261106800e-01
+6.1881838066429617e-01 1.7722838322154961e+00
+6.1415333469871580e+00 2.1400837096319969e+00
+4.1141125706338535e+00 1.5475862054690883e+00
+1.7964556238958695e+00 1.8586207344445518e+00
+5.3714723817119125e+00 1.0585994481983019e+00
+1.7343046370218618e-01 9.9358124225603017e-01
+4.8048487660130252e+00 1.8974876756217562e+00
+5.6193281599230556e+00 3.8066097921169995e-01
+3.8842742907867729e+00 1.8536780857818962e+00
+4.5185694017140037e+00 2.2372125433520380e+00
+2.9058485589326368e+00 2.5243392025747768e+00
+4.7510503681458669e+00 1.9315259609041988e+00
+1.5803189520723571e-02 4.2676539706876215e-01
+1.1790709665077403e+00 1.2808381667414572e+00
+6.0463749662643229e-01 2.2484681910808582e+00
+1.1054959822267865e+00 1.8834812723357690e+00
+5.8864902084741288e+00 1.8763992614599296e+00
+5.2867215697957146e+00 6.6294288560877634e-01
+9.0820535098714927e-01 1.5079345107291209e+00
+4.2134718878160839e+00 1.3907043001427417e+00
+2.2528627059016904e-01 9.2695665180032782e-01
+9.1635292723027639e-01 1.8681614941585187e+00
+1.1505944461608875e+00 2.2023108418631741e+00
+4.0918853077515545e+00 8.1765159879864535e-01
+5.3395329438250982e+00 2.5756594064885867e+00
+5.3525199674448247e+00 2.5174311669395220e+00
+3.1062837193679318e+00 1.9492031261619049e+00
+4.9192017106360302e+00 7.4241692581719565e-01
+1.7508911478791047e+00 1.6420973051788992e+00
+6.0826235185253736e+00 1.9398445796581265e+00
+6.1816421864601203e+00 1.6372697794486553e+00
+5.2322258753422446e-01 1.2201980785736330e+00
+2.1176642496079503e+00 1.3894118433771077e+00
+1.7062137316934629e+00 8.3292769955807000e-01
+1.3287964715966811e+00 2.5540069398149376e+00
+4.7924459725875952e+00 2.0271579648983593e+00
+2.3208041498532035e+00 1.1622678036233640e+00
+9.3796688111577309e-01 1.7033869188962869e+00
+1.0326690397901310e+00 1.9532967801347412e+00
+5.5602436002735107e+00 2.7087250286604050e+00
+5.9113261735129479e+00 1.9347684712988515e+00
+3.2386020379049230e+00 1.7001770441454698e+00
+1.5863908068210986e-01 1.3712076119512218e+00
+2.6003317448397150e+00 2.1277106768976743e+00
+7.2682756154894745e-01 8.1954373972694039e-01
+5.9492432131855661e+00 6.5717822589695263e-01
+1.6903955693371830e+00 5.0275494259016140e-01
+5.6137996619270147e+00 1.4210704818349038e+00
+5.6343541521726088e+00 1.9691229357579587e+00
+4.9219920013975065e+00 9.3254675534228437e-01
+5.7189739536853317e+00 1.5674498421375915e+00
+1.1784748279198487e-01 1.3964679368573032e+00
+5.9940372622666036e+00 1.1844821928065616e+00
+5.1592355879310325e-01 2.5552442026733724e+00
+1.8439301233892449e+00 1.1156753828272516e+00
+3.0140062654191349e+00 1.9723114215894322e+00
+6.0285742611770576e+00 2.1190367917072512e+00
+3.5679354195927040e+00 1.8980166631535231e+00
+1.6912706048037041e+00 2.0516301240595407e+00
+8.0238631017871409e-01 2.0371691992423626e+00
+1.0089982541534459e+00 2.3863426402103238e+00
+1.6788682897403868e+00 1.2241685725820195e+00
+2.6727157634886511e+00 2.0158394563242203e+00
+2.4739013388399784e+00 2.1018160692493373e+00
+2.6519629143870178e+00 1.4614745121815567e+00
+3.8752269860468060e+00 2.6560195600390544e+00
+2.7938802471891213e+00 8.8272939081629986e-01
+6.2498176153656813e+00 2.7718841895463173e+00
+1.8492186344521677e-01 2.0496406130453888e+00
+2.4999500925340432e+00 1.9265444687012880e+00
+6.1768127764510590e+00 1.2539915740935641e+00
+3.3859557245597283e+00 1.3043754764233759e+00
+5.7225653933954277e+00 1.1354761610822484e+00
+2.0680412000227810e+00 2.0093803401209804e+00
+1.8858526797647563e+00 1.3117546117625523e-01
+5.4934450675422468e+00 1.9754987460434519e+00
+4.9405296815225874e+00 1.6242161032382196e+00
+2.5081107342562166e+00 2.4033299002232464e+00
+5.5067005490090040e+00 6.2698841078928880e-01
+2.6708000629109150e+00 2.1046315082137288e+00
+2.9187220175031849e+00 2.5749924170423171e+00
+2.6076713442569317e+00 1.8780946918643964e+00
+2.6345531971132479e+00 1.6736737462461337e-01
+4.4045246914104199e+00 2.2212335936693104e+00
+6.1349568969925317e-01 2.0151810208785834e+00
+4.6348842052435657e+00 1.5433931748150387e+00
+8.5914357214632719e-01 1.3143422216229310e+00
+3.0570862516766439e+00 1.7922459316741433e+00
+1.5429917062369545e+00 1.1963467354588122e+00
+3.7061981851394274e+00 2.4095457024567448e+00
+1.4165785660928403e+00 2.5493285165835133e+00
+6.0749900781033714e+00 2.7056226911540260e+00
+3.6014487385464822e-01 1.4608390867555083e+00
+1.2989358412811283e+00 6.7128070482049729e-01
+3.8931345386191873e+00 1.3789445337949373e+00
+1.7957956111206324e+00 1.5054952003452062e+00
+9.0366881422739187e-01 1.2639360257264305e+00
+1.1494017221469834e+00 1.7360121894432767e+00
+6.0167861937990690e+00 2.0157117240349258e+00
+1.8816024883239622e+00 1.3440392290986924e+00
+1.4607895545695324e+00 1.3024481066542690e+00
+4.1686365221679971e+00 1.0033967938448334e+00
+6.2708181645692456e+00 2.1083071681899970e+00
+4.3754306073560878e-01 2.0175649590002296e+00
+1.8713032269706602e+00 2.8471100080202021e+00
+1.3896937887277201e+00 1.9833247524463271e+00
+4.4662402097982685e+00 1.8707622955176735e+00
+4.4207058704710231e+00 1.2337125601182266e+00
+5.3428765116730226e+00 1.3229711803414093e+00
+3.4919402898089391e+00 9.9812646181873788e-01
+3.5695541665425696e+00 6.5940197160648517e-01
+3.3445273579446670e+00 6.6604643459548152e-01
+5.4236009458485555e+00 2.5709113152810641e+00
+2.8073433757913575e+00 1.5710909798840240e+00
+2.3982960296434546e+00 2.6961053761447142e+00
+3.6415495230695187e+00 8.9972178879298892e-01
+1.9536822227570401e+00 1.9751148203354834e+00
+6.8655829642929422e-01 2.1573944485112615e+00
+2.3717574115485522e+00 2.0831718926159768e+00
+1.3507745717392305e+00 2.3828451566376536e+00
+3.9931663032303737e+00 2.2889937109906664e+00
+4.8970142629605142e+00 8.0272497245786500e-01
+2.7477576499863754e+00 1.0734238779839516e+00
+3.9073768946396155e+00 1.0452014898821389e+00
+1.7765048279155404e+00 2.0697363808630032e+00
+3.0357080206357629e+00 2.3725130981298177e+00
+1.9289733806061167e+00 2.6958663120243695e+00
+5.1902997401403503e+00 2.0038962486670977e+00
+4.6719107778809015e+00 1.3027673422399662e+00
+9.5168520874178242e-01 1.2845876544842609e+00
+4.0821886644046312e-01 1.4081827926174533e+00
+3.2562977002118769e+00 2.2583632896836683e+00
+4.6782842717746753e+00 1.7912165236084605e+00
+5.2443965300774380e+00 2.0141200502395735e+00
+4.6126675113127806e+00 7.7581919925421150e-01
+5.5046666511451949e+00 1.0007820739345306e+00
+1.0088009989188444e+00 7.6436012515322194e-01
+1.2515345294031224e+00 1.7517404924185804e+00
+5.4768862137749803e+00 1.0444884571952762e+00
+2.9663079365590552e+00 2.4959796874391467e+00
+1.2261344742759877e+00 3.7507181462490924e-01
+4.5469969844552995e+00 1.2012025848009036e+00
+5.4679219247361104e+00 2.0317780563035308e+00
+5.0897227470513240e+00 1.1245967670075130e+00
+5.2887511823897473e+00 1.5727381078415650e+00
+3.3872735914129151e+00 7.1100081355734801e-01
+1.3477128816278912e+00 1.9177932807595461e+00
+3.2680480188657786e+00 1.4365582249623416e+00
+5.3362398745881992e+00 2.1290623550343066e+00
+1.3228461399428195e+00 2.4313179551329478e+00
+4.5630818140687150e+00 2.0918640541529467e+00
+5.4365597610264507e+00 1.8712782873720204e+00
+2.9536705867025965e+00 1.1459147890838302e+00
+2.6271187242811456e+00 2.7230433389135502e+00
+5.9038508727583281e+00 1.1488417287506127e+00
+3.9590663512348114e+00 2.0969946688430641e+00
+5.0911774436439998e+00 6.7686948708181716e-01
+2.5853714590602741e+00 1.5058606144603384e+00
+2.1031334485281485e+00 1.6728302630023286e+00
+2.3613258112497562e+00 2.4001708314347967e+00
+4.5792637101083447e+00 2.0506802174548358e+00
+1.1928222374851352e+00 1.7388025005215026e+00
+3.2500947333030217e+00 3.3791371070737819e-01
+7.2688998366849500e-01 1.5417970936692889e+00
+8.9901506639737905e-01 2.4136886949331213e+00
+5.9693544751052041e+00 5.4295658366415167e-01
+3.9418171119926950e+00 1.6498032075641009e+00
+2.8036970413256137e+00 6.6225616408295385e-01
+4.4820065569460326e+00 2.0021489955822158e+00
+3.2972051146969639e+00 9.8621824349256304e-01
+4.2833448054641865e+00 2.6542788380833571e+00
+4.9557782183298720e+00 2.1495008810674996e+00
+3.8892899377150876e+00 9.5946149159815208e-01
+3.0927230877466569e+00 1.1303080221055906e+00
+3.0524841865206347e+00 6.0489110051693218e-01
+6.9518371971175918e-01 1.3856392428936761e+00
+5.7224627029244051e+00 1.4415086194142264e+00
+2.7795116289889830e+00 1.0327426356110778e+00
+2.5618956900488472e+00 7.1071755460756525e-01
+1.9835923633685137e-01 6.9427546810716356e-01
+3.2477184720054559e+00 8.0929575545126586e-01
+6.0578273583023252e+00 1.1725145712369800e+00
+1.7411977579790505e+00 1.7381794396472181e+00
+4.5902796603623672e+00 2.6843475961843133e+00
+6.8180553739668515e-01 2.1045476533104628e+00
+4.9476819453366252e+00 2.9037232050557824e+00
+4.8399869751247344e+00 1.7970862030360364e+00
+3.4362309063626992e+00 5.9115895691529818e-01
+1.0580050547083619e+00 1.9978696560222526e+00
+5.8107626045740153e+00 7.3291025884475469e-01
+1.2163759674292809e+00 2.3544811907966356e+00
+9.0857631376082271e-02 1.9377338657354168e+00
+3.7561211892995336e+00 1.7421360596976794e+00
+3.8598365056158261e+00 2.2332244045264109e+00
+2.9327811791300822e+00 1.0492803587966391e+00
+3.6754154065621227e+00 2.1547102564574825e+00
+4.5996137870447527e+00 6.5917296712226670e-01
+2.5764214278245694e+00 1.9815521838847050e+00
+3.7876073955344571e+00 2.2683755963531427e+00
+2.6517048494796760e+00 1.1147406453150976e+00
+7.7302696415136163e-01 3.9178760718061212e-01
+2.2291524662257975e+00 6.7238328924161406e-01
+3.3768232592601093e+00 1.1258738792407958e+00
+7.2950102024243124e-01 9.3795238678234705e-01
+3.8679337153741145e-01 7.4980295504932049e-01
+1.8354958785575040e+00 6.5472628456058923e-01
+4.3279229266500616e+00 2.1319981928748661e+00
+2.8550487600742764e+00 1.3321640504491712e+00
+3.0727042477845412e+00 2.1061321578340682e+00
+3.2195983979186282e-01 1.6747239478719831e+00
+4.3733413803713317e-01 9.7908417621962041e-01
+2.1728072082500711e+00 1.1015983868558907e+00
+2.1934265471836261e+00 2.0954749173366753e+00
+6.2231140587025449e+00 1.0840620425483394e+00
+4.7768283251392889e+00 6.1447499875740774e-01
+4.0901828252828212e+00 7.6733080547807331e-01
+5.5086396570265048e+00 3.5812836050787822e-01
+3.1535504563751933e+00 2.3433657651026834e+00
+2.3688445912729805e+00 8.1593186115117422e-01
+5.4417392892583756e+00 9.9609095486413457e-01
+1.5463541795156039e+00 9.9627867160772365e-01
+1.0285447969164521e+00 1.6706979681837997e+00
+4.1766941610789319e+00 1.7851731818512095e+00
+1.5888784728067615e+00 2.4842330004826585e+00
+9.7947470811288806e-01 1.6282743065516319e+00
+1.6810521678799002e+00 2.4036259534846072e+00
+1.8716672161631034e+00 1.9791272173102130e+00
+2.1714230953775004e-02 1.7756430998080264e+00
+1.4782744586081638e+00 5.5167777387010553e-01
+4.2889145126314876e+00 2.2497653285630625e+00
+6.2060366483496647e+00 1.3157496954858066e+00
+1.6680578499704302e+00 1.7855558603576458e+00
+3.9300037527432563e+00 2.2827881595349058e+00
+6.0809897631576675e+00 2.7567449667722106e+00
+3.0628041261943295e+00 9.2193866632622068e-01
+1.9165207208532689e+00 1.9436583427832734e+00
+3.6770228618584508e+00 1.0691779172784297e+00
+3.3356740926423729e+00 1.3625011113952556e+00
+7.8092928422023622e-01 1.9943208872819600e+00
+3.4739617800392359e+00 9.3137508554594817e-01
+8.7446599638100864e-01 6.2830341312907467e-01
+1.5843853028231456e+00 1.3990387067338268e+00
+3.3108090186330279e+00 8.8920477208607507e-01
+5.1113762930652324e+00 1.5128071203712676e+00
+4.5312319417827673e+00 8.5354209759777200e-01
+3.1900136626825484e+00 2.7267216223291943e+00
+5.5669339548672081e+00 8.2966017271902781e-01
+5.9864466917087125e+00 2.4379238914949894e+00
+2.9059862985345331e+00 2.3321584777404216e+00
+1.8755727387946193e+00 1.0612018768071343e+00
+5.8102422173014627e+00 2.1639123684450610e+00
+3.1056538881917248e+00 1.0641943627807371e+00
+5.4945444026298613e+00 7.7301850862774968e-01
+1.2945424732787003e+00 2.2512708108982169e+00
+6.9766281958110321e-01 1.0556338781490231e+00
+8.2145250055697439e-01 1.3515859002844632e+00
+3.5905118049372811e+00 1.5847019991546822e+00
+3.0307368168923245e+00 3.7987578657443066e-01
+2.3539544710581897e+00 2.2027598422257411e+00
+4.7347961724923096e+00 2.1805990742680708e+00
+2.9273708516631922e+00 1.1979648070507096e+00
+4.4020141261000356e+00 1.0513253893221108e+00
+5.7975231520249064e+00 1.8182930210271673e+00
+5.1689479308277138e+00 3.2764143162181747e-01
+3.6234523042813560e-01 2.7663994565279371e+00
+6.0181958147857060e+00 1.9108341537393414e+00
+5.5385321566599046e+00 1.2747325901730826e+00
+4.0536688367095701e+00 2.7374769436088267e+00
+8.8249949117598092e-02 2.4033370339132638e+00
+6.1926193835553622e+00 2.0780454641132873e+00
+2.4664073742336692e+00 7.3139531531925828e-01
+2.5544682428734364e+00 5.9947566934604468e-01
+3.3508977040705088e+00 2.9718194102812641e+00
+3.3793270917306275e+00 2.1322951874843756e+00
+6.0437049816177524e+00 1.4112117071089021e+00
+5.2936771652493055e+00 3.0107947133583113e-01
+3.2185058441219923e+00 2.7933853693212352e-01
+5.4891905362641946e+00 1.3139914587556532e+00
+2.5321600644616384e+00 4.2855776293914483e-01
+2.9468753180724905e-01 1.8314729958256843e+00
+1.9479114017801773e+00 2.1397674093726113e+00
+2.0700689727398203e+00 1.4319695913407384e+00
+2.8674995522837450e+00 1.4154448829000070e+00
+3.0345534417284461e+00 2.0682143985993156e+00
+4.4716146553313729e+00 7.1111907730714474e-01
+2.3053958095176386e+00 8.3896324015016344e-01
+5.8869605311934743e+00 1.3002593879436504e+00
+1.7219901629697008e+00 1.0512281185292518e+00
+4.6850612319863050e+00 2.0537282956299667e+00
+5.7158302609230702e+00 2.4015600960646841e+00
+3.6894752023745325e+00 2.3633450400270721e+00
+5.6885448982680398e+00 1.2488415073555974e+00
+3.3569549798289700e-01 1.9158426829215154e+00
+3.4545583576000287e+00 1.3031470212992724e+00
+1.6413565501858240e-02 9.8422288364020716e-01
+9.6845261351972800e-01 1.0946900033640559e+00
+2.5915262891147846e+00 1.1175704893096841e+00
+1.5025392764040613e+00 2.6166031747192759e+00
+3.2993821029281318e+00 2.1405211471302232e+00
+1.9559757090065346e+00 1.6258160671223743e+00
+4.4545206482101838e+00 6.1813543883635114e-01
+3.6806966566666159e+00 1.4841813520324867e+00
+1.5397679381505471e+00 2.6561296275320929e+00
+3.9493847150857642e-01 9.3147403581542521e-01
+1.1365657299682352e-01 9.9970954171476067e-01
+1.1760749446434497e+00 1.5521221727069145e+00
+3.0584960047815666e+00 2.8184070288482936e+00
+3.9526999513949908e+00 3.4437219901492000e-01
+7.9995528743306155e-01 2.1563139622085132e+00
+7.9735692519988488e-01 6.1727590339472527e-01
+1.9647077041378012e+00 1.6801426052080135e+00
+6.3412715223924376e-01 2.6081831787206955e+00
+2.4839902068758413e+00 2.4913642185735290e+00
+1.7485908643481809e+00 1.8407092075106029e+00
+3.9547223504038760e+00 1.5254805069972817e+00
+1.3586281682793402e+00 1.8151839159291323e+00
+4.5230907719395894e+00 3.0570868356716687e+00
+1.1629136784683340e+00 1.1277601857674555e+00
+6.3036533812169671e-01 1.8743985409058286e+00
+5.7380684444449574e+00 1.2802602892522990e+00
+3.4623591816242931e+00 2.3588647859521128e+00
+2.6107486772552391e+00 1.3466237326660522e+00
+7.3645796451264056e-01 1.7304006491317125e+00
+3.4645104056106359e+00 1.1694337335153913e+00
+7.4246157018247416e-01 2.2533331065110467e+00
+1.4489369142176207e+00 6.4817866325676154e-01
+8.8876184579874673e-01 2.2315019788777160e+00
+2.0787621074801579e+00 2.5171417245178578e+00
+2.2116265339404748e+00 1.0323014925578597e+00
+5.8505135413110061e+00 6.3346497735932950e-01
+5.4994748831312092e+00 2.0684115730230084e+00
+5.0118599936941157e+00 1.3455545936852595e+00
+6.0021509868277789e-01 2.5676231220859824e+00
+2.6352011271920044e+00 2.0719464772934879e+00
+2.5045674436545458e+00 7.6646307617740617e-01
+2.6032370270501102e+00 1.2150453874686677e+00
+6.0895149517819895e+00 1.5765709020753975e+00
+1.4482904729556663e+00 1.8663356547888463e+00
+7.0719967358917035e-01 2.2029974286993745e+00
+5.0566493084380912e+00 2.1666532252308492e+00
+2.7734787009035555e-01 9.9095971151775075e-01
+6.0424088431071414e+00 1.7693122618866690e+00
+5.0651960643031435e+00 8.4882129775226511e-01
+6.0440973510864477e+00 1.5487465437335268e+00
+4.7663075658038521e+00 2.5295902701822812e+00
+3.8727781877144096e+00 2.7078132117323848e+00
+6.2263049948686326e+00 4.5793047102571771e-01
+3.5051911053891835e+00 1.3096891769612573e+00
+3.3108416625564754e+00 1.7067854663511794e-01
+1.6227609214326968e+00 1.6071576746384868e+00
+1.3222617844534403e+00 2.1554554760775191e-01
+3.0473305109767570e+00 1.4712708055666854e+00
+4.5170213693496484e+00 2.1309698430830215e+00
+6.1614902390994901e+00 2.4193288348583253e+00
+2.1330753941914056e+00 2.4315976627484885e+00
+1.3810346811510508e+00 1.0908735842029733e+00
+2.2308632373899759e+00 2.3594675474084967e+00
+5.6252274528421298e-01 2.2180685981905341e+00
+3.9582275469275094e+00 1.7450704988730166e+00
+2.4349521975142396e+00 1.8736694260943576e+00
+5.6090670814776891e+00 1.6318403357915499e+00
+2.6816144356622451e+00 2.4573813911432052e+00
+5.5259744698597748e+00 2.2890625761408057e+00
+1.4112399544290868e+00 1.2643249196619046e+00
+5.0418085308294573e+00 9.0027203251974974e-01
+5.4273945512039283e+00 1.8234116576258663e+00
+2.6730451922433596e+00 1.9555119187775989e+00
+3.6362086624269181e+00 6.3619008802117472e-01
+5.6455994851578772e-01 1.0859452665574914e+00
+1.5602630132068340e+00 1.7445086157905094e+00
+3.7367790616358367e+00 2.2971356473648474e+00
+3.1412211717555611e+00 2.6685504779633513e+00
+5.7003352530241020e+00 4.1524387004836849e-01
+4.2004632384658702e+00 1.6969198527211165e+00
+3.0675583900882510e+00 1.3691450744545854e+00
+4.0161317170742343e+00 1.5570445523528738e+00
+4.9547621724902413e-01 7.0796476283457066e-01
+2.7162680868177480e-01 2.2354445943297350e+00
+4.6674648595594554e+00 2.4992254501397415e+00
+2.0307557392944671e+00 2.6377590102063708e+00
+4.1756834756490857e-01 2.7152599632791605e+00
+5.7815663484258790e+00 1.1977640280160335e+00
+3.0347596421362555e-01 1.5425654573046279e+00
+3.2907174620394430e-01 5.2116408289789251e-01
+3.5660902560679970e+00 1.7276907027738297e+00
+2.7918195746111851e+00 8.0790302491837085e-01
+1.5398521818877731e-01 1.0985194049838036e+00
+2.4616220609847961e-01 4.3132857281774517e-01
+1.4633238281143055e+00 1.5997961514773507e+00
+7.8273404816285019e-01 1.6836385713744837e+00
+3.9267719863660986e+00 1.5671753469265575e+00
+3.0082980489097655e+00 1.5553551427712657e+00
+6.7508178108234596e-01 1.5788750052699216e+00
+2.7685370994954903e+00 4.2117061646266674e-01
+5.6493362811829249e+00 8.1226525156066731e-01
+3.5128439229354393e+00 1.9140306162513050e+00
+4.6445692049681195e+00 1.1210016477906919e+00
+6.1374205050101764e+00 7.6143177323842881e-01
+3.6618628609457065e+00 1.9795655394668756e+00
+3.1297765818365626e+00 2.2769644247931127e+00
+3.2710681953715071e+00 2.5616050515347886e+00
+3.8220261065980958e+00 1.0704943071233233e+00
+4.7847345756577200e+00 1.5330008469936522e+00
+3.7049690457260360e+00 1.6771629773545922e+00
+4.6039809507207385e+00 9.5118404589243366e-01
+2.7104198399119617e+00 2.1663533902375303e+00
+4.0941396448154226e+00 1.9435520623044755e+00
+1.0454566202957221e+00 1.1328329152317209e+00
+5.5048882418841485e+00 1.6186716756894077e+00
+1.5508619584195826e+00 1.7885685178051873e+00
+2.3021731464080859e+00 2.0608059683911861e+00
+1.9386878636715226e+00 1.3700432750376330e+00
+6.1050099721896576e+00 9.2474861451600487e-01
+1.2924290606069406e+00 2.6498315695977768e+00
+6.2881683441192360e-01 2.3369882441782694e+00
+4.3860421823027291e+00 1.5912373874976073e+00
+5.5289949175121667e-01 6.7372139927663111e-01
+1.2209472961969370e+00 1.9439287427829757e+00
+3.7857703208658871e+00 2.0487213679189105e+00
+4.5148178523333167e+00 2.6542787314651415e+00
+5.4872529097762053e+00 2.6699705527334281e+00
+3.7507308277682596e+00 1.4075171263965724e+00
+5.3276147214806819e+00 8.4507661645301868e-01
+4.3127233977716353e+00 1.8891904516398477e+00
+4.9177963637772635e+00 1.8968345480077808e+00
+3.3556539249731316e+00 2.7752476760496529e+00
+7.6499869076362970e-03 1.7289472807104864e+00
+1.3065187269032537e+00 7.6241349871149122e-01
+2.6473587835555525e+00 2.8240913870214488e+00
+3.4434771799551633e+00 2.3129850776767373e+00
+2.4832930388839740e+00 8.7384374970539713e-01
+4.2070552507755083e+00 2.9530893092331256e+00
+3.6771932072799300e+00 1.6306261532206030e+00
+4.8329032321738454e+00 8.0962012990294563e-01
+1.1197949946890544e+00 5.8331075131591514e-01
+3.1573469978002984e+00 1.5416905722274590e+00
+2.4391635698957153e+00 8.4388540002009871e-01
+6.1134476295322937e+00 1.4804613116898093e+00
+5.8304057599571850e+00 9.4178255319396587e-01
+5.6129967215991634e+00 1.9006014538257534e+00
+2.7572568712129786e+00 2.4346006259915591e+00
+4.7784042507853588e+00 1.1809906437321849e+00
+5.7036708733022250e+00 1.0641142756207880e+00
+2.3759539137045316e+00 1.7713155120556594e+00
+7.4399602690611211e-01 1.8939185700833971e+00
+3.1890546098343644e+00 2.5692783494851241e+00
+2.2189719105597985e+00 1.8326495035726009e+00
+5.3264975184952394e+00 1.8446902227939133e+00
+1.1258228232242340e+00 2.1207104451492551e+00
+1.4481312868891347e+00 6.0129282504671111e-01
+3.3212283097049191e+00 1.2213815221292579e+00
+5.5935601464233553e-01 1.6923959193495426e+00
+1.5063706835191044e+00 1.3413754340916810e+00
+3.9721029078515944e+00 1.3196226932654962e+00
+7.7417939443713468e-01 2.5226934279984103e-01
+3.8334190054051418e+00 9.6282185972695944e-01
+5.5802094603946744e+00 1.1389827238390704e+00
+2.9889968225608623e+00 7.6605505564354170e-01
+1.9808966274159825e+00 2.9502896704073769e+00
+4.7585650440612799e+00 1.4577163778619611e+00
+5.8533564590010982e+00 3.3746402702042522e-01
+4.0912436779751413e+00 1.5027164190496753e+00
+3.0816271383831979e+00 2.5776483739215541e+00
+2.6983857673566658e+00 1.4510564117639690e+00
+1.1679853163965637e+00 2.1634232524247579e+00
+2.7841396272634764e+00 1.6853995174403138e+00
+3.1072849799435884e+00 2.4779412387758994e+00
+5.4242327247797562e+00 2.4106168597891831e+00
+4.4836257333484388e+00 9.0950984629929399e-01
+3.8789185451068700e+00 2.1488406862282536e+00
+5.3221217819923172e-01 1.5571664286247699e+00
+6.6306237394662793e-01 1.5378392845813407e+00
+3.7655614482067730e+00 2.8732048078831824e+00
+2.8659105727722407e+00 7.9695251691733437e-01
+3.3661435279118415e+00 1.6010041658458107e+00
+1.2042001226103638e-01 2.8572522390150001e-01
+3.6333730100293220e+00 1.4006231978913424e+00
+4.0483208110644968e+00 6.9441459764151103e-01
+2.9782711876731622e+00 2.0608503180402522e+00
+1.0412779568761188e+00 2.4397594890049783e+00
+4.7146188913799723e+00 2.3596766916535410e+00
+4.9488862249170058e+00 2.6493365786144771e+00
+1.7833591413251129e+00 2.2415912345424176e+00
+3.2078502000211797e+00 7.7073826973120785e-01
+1.4943610809920180e+00 1.0200612084529488e+00
+1.2597432160475626e+00 5.1271190280943357e-01
+5.0368457187685980e+00 2.5368722767067009e+00
+4.0335953989883322e-01 2.5959925494819092e+00
+1.2980958364749917e+00 2.7651550716018902e-01
+6.1544753262199663e+00 1.5359372922782504e+00
+3.5290291651518597e+00 9.1007480207416758e-01
+3.6144043140308590e+00 1.9517453276772363e+00
+6.0246417673339341e+00 2.4005353145566461e+00
+6.1458390304879806e-01 1.5385923321504098e+00
+5.5352116036235355e+00 1.7137647911033740e+00
+5.5674358715692600e+00 5.6989038002334458e-01
+1.2741244447374214e+00 2.3094299655190937e+00
+1.0827147753931587e+00 2.1978834544572825e+00
+4.1384224681263584e+00 2.3714522363050534e+00
+1.1044955596357990e+00 4.0392277084229322e-01
+7.4491808701218698e-01 1.0441129934591873e+00
+2.3100779950393564e+00 2.2316535839934688e+00
+2.5402690500035585e+00 1.2872441011676037e+00
+5.6014081673551308e-01 2.1266152086823165e+00
+4.2863594307176331e+00 1.6406362606032181e+00
+3.5059045131330868e+00 4.2334386622979325e-01
+6.1984474896308370e+00 1.4456260735392568e+00
+1.2985100403931460e+00 2.3491621826245739e+00
+5.7089364574453523e+00 1.3128768664338144e+00
+4.2735230695070072e+00 2.3076637250021164e+00
+4.6057441375710750e+00 2.5581169605775202e+00
+2.4349609028583168e+00 2.1486362275584532e+00
+2.9945178268608310e+00 1.5098096111831407e+00
+6.6109626640970109e-01 1.2792536652586630e+00
+5.5620295842983039e+00 1.6235475972446431e+00
+2.8086660985142866e+00 2.1464535824957824e+00
+4.2688563555630363e+00 2.1508847617250044e+00
+4.6829023196942003e+00 6.4027381369471559e-01
+1.8004424151293237e+00 1.9517149709383954e+00
+3.6863258381120896e+00 2.1088693107548639e+00
+2.3405189931557677e+00 6.4422004595124260e-01
+4.9684583691360178e+00 2.3762948879779882e+00
+4.6348448820117394e-01 1.2329432896040533e+00
+6.2316939959122415e+00 1.9148785934726531e+00
+4.8722559571625119e+00 1.9258136535013117e+00
+6.1200173241263034e-02 8.2435216266057065e-01
+5.5885453605298352e+00 1.4856032935914716e+00
+5.3825009590551645e+00 1.2492367242559592e+00
+4.3953653971664775e+00 7.7288648911573032e-01
+5.5647211312151201e+00 1.3900783541868607e+00
+2.9262319207004404e+00 1.8388500400686247e+00
+2.3820143176410942e+00 2.1467205978281747e+00
+5.1386891769768406e-01 1.3192190685331524e+00
+6.2492206277081461e+00 2.0686583124884623e+00
+5.3741685380512170e+00 3.4426166491011312e-01
+6.2592434752300292e+00 2.3045200132163228e+00
+4.2937332115965221e+00 9.2441763115322839e-01
+4.0065506204475421e+00 5.2847467944340609e-01
+2.6158218290594064e+00 1.6712409952822718e+00
+2.9643867219036659e+00 1.3298428553552679e+00
+2.4729949605430948e+00 1.7570877033564121e+00
+2.9801073825809530e+00 3.3204453763369712e-01
+1.4507164815106766e-01 1.2714959758958171e+00
+2.7446406114830322e+00 9.8517166554259139e-01
+6.6202801050959703e-01 1.7849069740884413e+00
+2.4250192358605327e-01 2.3208457822529431e+00
+3.4311734516548515e+00 1.6078828380228491e+00
+3.7966588826877734e+00 7.6594845691123936e-01
+4.5758772095652436e+00 1.2449116307303212e+00
+1.5965822533579821e+00 1.6483616598771389e+00
+4.5672174774400167e+00 1.5376358081684809e+00
+4.4945717267793563e+00 2.3043519844708760e+00
+3.1927928429073598e+00 1.1237034038073301e+00
+5.5526493999025313e+00 2.1894926010664060e+00
+4.9340681591923605e+00 1.2185388938529287e+00
+3.1001982862115258e+00 2.3980148745690832e+00
+4.7404489858160197e+00 1.3609734545819778e+00
+4.2314528546458714e+00 9.2039015798971302e-01
+6.1441308622296305e+00 1.4308752229633113e+00
+7.2548260059614744e-01 1.1884468761471765e+00
+5.4781144702537281e+00 1.6752894258050381e+00
+3.5579347503834540e+00 2.0799513536440490e+00
+8.5967168815440598e-01 3.0193807965588837e-01
+6.0950831344777363e+00 3.6166207575339526e-01
+5.3048134285976811e+00 1.0618666965194326e+00
+3.7989244118915844e-01 1.0975861813106254e+00
+1.8108016851954325e+00 1.2638921531751555e+00
+2.5033554815227701e+00 2.5390688525195761e+00
+3.9058443121810837e+00 1.5100610132740877e+00
+5.7174333033813989e+00 8.2992043415692529e-01
+2.6472403453913094e+00 2.1500171574848697e+00
+4.4841886284561934e+00 8.1060590744329297e-01
+6.0694933940698510e+00 2.1776059978624578e+00
+2.7057097184971255e+00 1.6211100898626281e+00
+4.0075802748776859e+00 1.9775269134754947e+00
+5.6538327601947103e+00 1.8753919724988224e+00
+5.4973744837626217e+00 5.3335575674516367e-01
+9.6667582613744463e-01 2.8061826229686799e+00
+1.2916435260467594e+00 1.1282929752781792e+00
+3.9022400716083410e+00 1.7903074403240429e+00
+4.8415606326771039e+00 1.3726489230300896e+00
+1.5747525058536493e+00 1.9162149780196529e+00
+2.3196371818602195e+00 7.8391814813991767e-01
+2.0877401237205229e+00 7.5939988159354366e-01
+2.8923180144325880e+00 1.5477988592753982e+00
+1.1267014758012550e+00 2.7448064464452138e+00
+4.6885348547644643e+00 1.4939739288862490e+00
+6.0756048259878437e-01 8.7771584102638500e-01
+5.5692370837153558e+00 1.4415777162738921e+00
+4.7071567747945409e-01 2.1541373723836243e+00
+4.9563672319577492e+00 1.9215951460557965e+00
+5.3217478020686091e+00 2.6238722321298429e+00
+2.8865610265439847e+00 1.8086486236970070e+00
+3.8096799516291560e+00 2.1443105833285210e+00
+2.2612668407012526e+00 2.0218824418583834e+00
+4.7970987084723085e+00 1.6281918400550948e+00
+3.6828913532002199e+00 2.5246635008208065e+00
+3.9035453869963974e+00 2.6154295142966184e+00
+5.3135984453585676e+00 8.9936020351722312e-01
+2.8276795014938800e+00 2.5674533007866884e+00
+5.5442054966627623e+00 1.9601614248300900e+00
+3.0175452688540068e+00 9.5482458700406680e-01
+3.8346123160163348e+00 1.6755572715670772e+00
+3.5292416521462431e+00 5.8388008154344173e-01
+4.6105789191804334e+00 1.4481814958718053e+00
+3.3976716781073293e+00 1.7362903760778403e+00
+4.9243601190736186e+00 1.7464379893962489e+00
+2.6922080535552713e+00 2.6303889572584511e+00
+5.6733896704642444e+00 1.1254351097247448e+00
+5.4835496734282518e-01 1.1290112002209662e+00
+6.2805116279923592e+00 1.0549536029757198e+00
+4.5911726596024289e+00 1.1601997688098817e+00
+3.5115497979449106e+00 2.0623834581676546e+00
+7.9467400314330572e-01 2.8883653436886392e+00
+5.2886906324382785e+00 1.8860150096228325e+00
+4.6539227675600827e+00 5.9821554614616324e-01
+2.5898457703966167e+00 1.0410782588690082e+00
+1.6879173156742091e+00 2.0072252640379533e+00
+2.0213136921082508e+00 2.3230639773613300e+00
+3.6074251122816610e+00 2.3173943645692172e+00
+4.3294708676583209e+00 2.5467225095885020e+00
+5.4038147007321324e+00 1.6917532930455463e+00
+1.2816300262236691e+00 2.0736151560602352e+00
+3.1267993329677464e+00 2.1464011577964199e+00
+2.6507126632595939e+00 2.3031926706722743e+00
+1.5033244939357113e+00 1.6461540526204324e+00
+4.7894293853504042e+00 1.0658791928104028e+00
+4.9531341838395919e+00 1.7909411303749356e+00
+2.4465987609158377e+00 2.3083878387624690e+00
+2.0076446400978512e+00 2.3980186868968345e+00
+2.9933299153246331e+00 6.4414509357515004e-01
+1.7121691792119604e+00 1.8777881352820565e+00
+1.0773883217002345e+00 2.2891648385774688e+00
+2.5712129453683579e+00 7.5650118984210712e-01
+1.8168551189709814e+00 9.1528562555877158e-01
+1.5454187511464426e+00 1.0654940138022257e+00
+4.0898571274342546e+00 1.5877007788820496e+00
+5.9754002592271327e+00 1.7497715144454211e+00
+1.2123698940618988e+00 7.4965400027640738e-01
+5.6489557510066639e+00 2.2709003456546739e+00
+3.6263448931843367e+00 4.8100919177691770e-01
+1.2614149612071774e+00 8.4877106986514272e-01
+4.4527149049164070e+00 1.1966656623073002e+00
+4.1281590143050462e+00 1.6241387453179255e+00
+2.0183268867412014e+00 6.3228000361781844e-01
+3.3811482304579132e+00 2.4579557132836594e-01
+2.4281815130964501e-01 2.7847536846558421e+00
+1.7379239799895041e-01 8.4588505568400407e-01
+5.1667912571077821e+00 1.2134760458142400e+00
+6.9280685725224611e-01 1.6244610200905483e+00
+4.3300505950752477e+00 1.4101042854611787e+00
+4.8620622552852533e+00 9.2285735048354933e-01
+4.9965046097286141e+00 1.2946583438951498e+00
+4.4231459604372656e+00 9.2742454509187278e-01
+5.9790500203855128e+00 2.6464892339283441e+00
+1.8639553921074399e+00 1.8036996327353940e+00
+6.1793550309034879e+00 1.7701642231966899e+00
+3.4430683746638207e+00 1.2173380906036111e+00
+4.1540961115607651e+00 1.2654872611812740e+00
+5.8621380196771735e+00 6.8303878784188865e-01
+1.4103987249754029e+00 1.3131758191021627e+00
+6.1620509034658451e+00 1.3905318225052248e+00
+1.1490606717177265e+00 7.3004378936721481e-01
+5.8684178613301965e+00 9.8619733821248146e-01
+5.9887668046270850e+00 1.0018102839826519e+00
+1.1434178046372698e+00 2.4382824269841636e+00
+2.9784574235709949e-01 2.1923808852472497e+00
+1.6608561320839468e+00 2.4712097204876629e+00
+3.4259851952149560e+00 7.6245243714423017e-01
+4.9076946726878958e+00 1.8044569798850378e+00
+3.7723721327672983e+00 1.6340905181737662e+00
+2.0063448508187203e+00 2.5110495040319165e+00
+2.5781138824116674e+00 2.7765636519215375e+00
+5.3044007277012906e+00 4.1726602423436199e-01
+3.9501372452362880e+00 2.3487085024774803e+00
+1.7041373726491615e+00 4.5405724880314935e-01
+3.1805694811438543e+00 2.1698095704862221e+00
+5.5985653369800357e+00 2.6107406737402705e+00
+1.0862448325783074e+00 1.2260976814400890e+00
+5.0581330831176743e+00 2.4883617400008706e+00
+1.2029332462135902e+00 2.6452079412991742e+00
+6.0953271586338253e-01 1.1190320893051124e+00
+2.4376859440114407e-01 3.0835088431619475e+00
+1.3240417913760716e-01 2.3011296762764371e+00
+6.0782759479919042e+00 2.0290781469159449e+00
+5.5504105203842373e+00 1.8903762368121615e+00
+2.8009697683918278e-01 1.1196767731409016e+00
+4.3117869903486232e+00 1.4467104072258652e+00
+6.1649913002550552e+00 1.5838107586016512e+00
+1.3868712238690566e+00 1.4277878968480073e+00
+9.0454807571611040e-01 3.0032532355995234e+00
+2.3310253292401941e+00 1.8640581918285830e+00
+4.6455863358442810e+00 1.4902220373340429e+00
+3.8524571270494938e+00 2.5144985616778270e+00
+2.7906215484972865e+00 2.0416307777935483e+00
+5.7385072044801611e+00 1.3736454536522422e+00
+6.2126478872390685e+00 2.9921982676964740e+00
+3.2253854103310080e+00 2.7895481856533957e+00
+8.6216377028437963e-01 1.7907276192555526e+00
+3.0527597788955836e+00 2.2405908446975484e+00
+3.5638358492044540e+00 1.9448365602045128e+00
+4.1114905422328931e+00 2.0721951289896055e+00
+1.2835170804809695e+00 1.9419944515746923e+00
+2.2384422731968447e+00 1.4090176118836388e+00
+2.1520712794275898e+00 1.4690153184560710e+00
+3.9062308096608791e+00 9.1065150001055262e-01
+3.6302253655070347e+00 1.7772035282519880e+00
+4.3334841380693065e+00 1.0992875545099461e+00
+9.9546706286490405e-01 1.2922095739377004e+00
+4.1163881549365939e+00 1.4526209693731869e+00
+4.8642796029693747e+00 7.0215717841062453e-01
+2.4821941114187109e+00 1.7005208600786925e+00
+2.4675263607046789e+00 1.4676926828407266e+00
+4.3705312587819654e+00 1.0097450366010863e+00
+1.4670338487688988e+00 1.4712850101639940e+00
+5.8515959155564659e+00 2.3629399226315750e-01
+8.7637058467932105e-01 2.4664547189086372e+00
+3.1581940144725991e+00 1.9795043947770776e+00
+1.8119231951858961e-01 1.7916275023692996e+00
+2.0079938912474438e+00 1.5958347607535264e+00
+2.0348272514811137e+00 2.1534495125334212e+00
+5.9638828508919213e+00 1.9521646732492202e+00
+1.2501427105634746e+00 2.1957996038395575e+00
+2.9616151486467124e+00 2.8753777762031003e+00
+2.7341465434991719e+00 2.9044695246472156e+00
+5.2226055349733960e+00 9.8884810112643828e-01
+6.1248338983427768e+00 2.9040204567007311e+00
+4.9694719677472516e+00 6.2353543632589825e-01
+3.3464572870945686e+00 2.2544754614147839e+00
+3.1833639161642635e-01 1.3759374943317093e+00
+3.2045738063287663e+00 1.5351048437233623e+00
+2.8385831533966921e+00 1.1186835269392359e+00
+2.9670919694021980e+00 1.8433821353559368e+00
+4.1173013195613715e+00 1.0458220685043136e+00
+3.1642402421295386e+00 1.5876648646085985e+00
+4.6363190923036122e+00 9.9353859338501826e-01
+5.9220996845290701e+00 1.8090191132073565e+00
+5.1016576653131702e+00 5.9373556167008135e-01
+2.7193565855667794e+00 6.2185813909171961e-01
+6.0230868267621496e-01 1.4500799668627540e+00
+5.8482293886130172e+00 1.5563031368922609e+00
+2.4048947163106211e+00 2.2453685960150946e+00
diff --git a/ear/core/data/README.md b/ear/core/data/README.md
new file mode 100644
index 00000000..0699fdf7
--- /dev/null
+++ b/ear/core/data/README.md
@@ -0,0 +1,17 @@
+# 2051_layouts.yaml
+
+Loudspeaker layouts derived from ITU-R BS.2051-1.
+
+# Design_5200_100_random.dat
+
+Approximate spherical t-design for t=100, as in [0].
+
+"The text file for each point set consists of two columns which contain the
+spherical coordinates (phi,theta) in [0,2pi] x [0,pi) of the quadrature points
+p = ( sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta))."
+
+Obtained from http://homepage.univie.ac.at/manuel.graef/quadrature.php
+
+[0] M. Graf and D. Potts, “On the computation of spherical designs by a new
+optimization approach based on fast spherical Fourier transforms,” Numerische
+Mathematik, vol. 119, no. 4, pp. 699–724, Dec. 2011.
diff --git a/ear/core/delay.py b/ear/core/delay.py
new file mode 100644
index 00000000..826c1896
--- /dev/null
+++ b/ear/core/delay.py
@@ -0,0 +1,49 @@
+import numpy as np
+
+
+class Delay(object):
+ """Multi-channel delay line.
+
+ Parameters:
+ nchannels (int): number of channels to process
+ delay (int): number of samples to delay by
+ """
+
+ def __init__(self, nchannels, delay):
+ self.delaymem = np.zeros((delay, nchannels))
+ self.delay = delay
+
+ def process(self, input_samples):
+ """Push n samples through the delay line.
+
+ Parameters:
+ input_samples (array of nsamples by nchannels): input samples
+
+ Returns:
+ array of nsamples by nchannels: output samples, delayed by delay
+ samples.
+ """
+ output = np.zeros_like(input_samples)
+
+ # transfer samples from the delay memory followed by the input, to the
+ # output followed by the new delay memory, such that concat(src) before
+ # the transfer has the same value as concat(dst) after
+ src = [self.delaymem, input_samples]
+ dst = [output, self.delaymem]
+
+ # copy the common part of src[0] and dst[0]
+ start_len = min(len(src[0]), len(dst[0]))
+ if start_len: dst[0][:start_len] = src[0][:start_len]
+
+ # copy the part where src[0] overlaps dst[1] or src[1] overlaps dst[0]
+ overlap = len(src[0]) - len(dst[0])
+ if overlap > 0: # src[0] longer
+ dst[1][:overlap] = src[0][-overlap:]
+ elif overlap < 0: # dst[0] longer
+ dst[0][overlap:] = src[1][:-overlap]
+
+ # copy the common part of src[1] and dst[1]
+ end_len = min(len(src[1]), len(dst[1]))
+ if end_len: dst[1][-end_len:] = src[1][-end_len:]
+
+ return output
diff --git a/ear/core/direct_speakers/__init__.py b/ear/core/direct_speakers/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/core/direct_speakers/panner.py b/ear/core/direct_speakers/panner.py
new file mode 100644
index 00000000..13b16436
--- /dev/null
+++ b/ear/core/direct_speakers/panner.py
@@ -0,0 +1,238 @@
+from attr import evolve
+from multipledispatch import dispatch
+import numpy as np
+import re
+import warnings
+from ..geom import inside_angle_range
+from .. import point_source
+from ..renderer_common import is_lfe
+from ...options import OptionsHandler, SubOptions, Option
+from ..screen_edge_lock import ScreenEdgeLockHandler
+from ...fileio.adm.elements import DirectSpeakerCartesianPosition, DirectSpeakerPolarPosition
+
+
+inside_angle_range_vec = np.vectorize(inside_angle_range)
+
+
+class DirectSpeakersPanner(object):
+
+ options = OptionsHandler(
+ point_source_opts=SubOptions(
+ handler=point_source.configure_options,
+ description="options for point source panner",
+ ),
+ additional_substitutions=Option(
+ default={},
+ description="dictionary of additional speaker label substitutions",
+ ),
+ )
+
+ @options.with_defaults
+ def __init__(self, layout, point_source_opts={}, additional_substitutions={}):
+ self.layout = layout
+ self.psp = point_source.configure(layout.without_lfe, **point_source_opts)
+
+ self.n_channels = len(layout.channels)
+ self.channel_names = layout.channel_names
+
+ self.azimuths = np.array([channel.polar_nominal_position.azimuth for channel in layout.channels])
+ self.elevations = np.array([channel.polar_nominal_position.elevation for channel in layout.channels])
+ self.distances = np.array([channel.polar_nominal_position.distance for channel in layout.channels])
+
+ self.positions = layout.nominal_positions
+ self.is_lfe = layout.is_lfe
+
+ self._screen_edge_lock_handler = ScreenEdgeLockHandler(self.layout.screen)
+
+ self.pvs = np.eye(self.n_channels)
+
+ self.substitutions = {
+ "LFE": "LFE1",
+ "LFEL": "LFE1",
+ "LFER": "LFE2",
+ }
+ self.substitutions.update(additional_substitutions)
+
+ SPEAKER_URN_REGEX = re.compile("^urn:itu:bs:2051:[0-9]+:speaker:(.*)$")
+
+ def nominal_speaker_label(self, label):
+ """Get the bs.2051 speaker label from an ADM speaker label.
+
+ This parses URNs, and deals with alternative notations for LFE
+ channels.
+ """
+ match = self.SPEAKER_URN_REGEX.match(label)
+ if match is not None:
+ label = match.group(1)
+
+ if label in self.substitutions:
+ label = self.substitutions[label]
+
+ return label
+
+ def closest_channel_index(self, position, candidates, tol):
+ """Get the index of the candidate speaker closest to a given position.
+
+ If there are multiple speakers that are considered equally close with
+ respect to a given tolerance, no decision on the closest speaker
+ can be made.
+
+ Parameters:
+ position (DirectSpeakerPolarPosition or DirectSpeakerCartesianPosition):
+ Target position
+ candidates (boolean index array): Subset of self.speakers to be considered
+ tol (float): tolerance for defintion of "closest".
+
+ Returns:
+ Index to the speaker in self.speakers that is closest to the
+ target position or `None` if no such speaker can be uniquely defined.
+ """
+ cart_position = position.as_cartesian_array()
+
+ candidate_position_indizes = np.flatnonzero(candidates)
+
+ distances = np.linalg.norm(
+ self.positions[candidate_position_indizes] - cart_position[np.newaxis],
+ axis=1)
+
+ min_idx = np.argmin(distances)
+ min_dist = distances[min_idx]
+
+ # if we find exactly one match within the given tolerance, use it
+ if np.count_nonzero(np.abs(min_dist - distances) < tol) == 1:
+ return candidate_position_indizes[min_idx]
+ # Otherwise, we either don't have a match or we have multiple matches
+ else:
+ return None
+
+ @dispatch(DirectSpeakerPolarPosition, float)
+ def channels_within_bounds(self, position, tol):
+ """Get a bit mask of channels within the bounds in position."""
+
+ def min_max_default(bound):
+ return (bound.min if bound.min is not None else bound.value,
+ bound.max if bound.max is not None else bound.value)
+
+ az_min, az_max = min_max_default(position.bounded_azimuth)
+ el_min, el_max = min_max_default(position.bounded_elevation)
+ dist_min, dist_max = min_max_default(position.bounded_distance)
+
+ return (
+ (inside_angle_range_vec(self.azimuths, az_min, az_max, tol=tol) |
+ # speakers at the poles have indeterminate azimuth and should match
+ # any azimuth range
+ (np.abs(self.elevations) >= 90.0 - tol)) &
+ (self.elevations > el_min - tol) & (self.elevations < el_max + tol) &
+ (self.distances > dist_min - tol) & (self.distances < dist_max + tol)
+ )
+
+ @dispatch(DirectSpeakerCartesianPosition, float)
+ def channels_within_bounds(self, position, tol):
+ """Get a bit mask of channels within the bounds in position."""
+
+ bounds = [position.bounded_X, position.bounded_Y, position.bounded_Z]
+ bounds_min = [bound.min if bound.min is not None else bound.value
+ for bound in bounds]
+ bounds_max = [bound.max if bound.max is not None else bound.value
+ for bound in bounds]
+
+ return (
+ np.all(self.positions + tol >= bounds_min, axis=1) &
+ np.all(self.positions - tol <= bounds_max, axis=1)
+ )
+
+ def is_lfe_channel(self, type_metadata):
+ """Determine if type_metadata is an LFE channel, issuing a warning is
+ there's a discrepancy between the speakerLabel and the frequency
+ element."""
+ has_lfe_freq = is_lfe(type_metadata.extra_data.channel_frequency)
+
+ has_lfe_name = False
+ for label in type_metadata.block_format.speakerLabel:
+ nominal_label = self.nominal_speaker_label(label)
+
+ if nominal_label in ("LFE1", "LFE2"):
+ has_lfe_name = True
+
+ if has_lfe_freq != has_lfe_name and type_metadata.block_format.speakerLabel:
+ warnings.warn("LFE indication from frequency element does not match speakerLabel.")
+
+ return has_lfe_freq or has_lfe_name
+
+ @dispatch(DirectSpeakerPolarPosition)
+ def apply_screen_edge_lock(self, position):
+ az, el = self._screen_edge_lock_handler.handle_az_el(position.azimuth,
+ position.elevation,
+ position.screenEdgeLock)
+
+ return evolve(position,
+ bounded_azimuth=evolve(position.bounded_azimuth, value=az),
+ bounded_elevation=evolve(position.bounded_elevation, value=el))
+
+ @dispatch(DirectSpeakerCartesianPosition)
+ def apply_screen_edge_lock(self, position):
+ X, Y, Z = self._screen_edge_lock_handler.handle_vector(position.as_cartesian_array(),
+ position.screenEdgeLock)
+
+ return evolve(position,
+ bounded_X=evolve(position.bounded_X, value=X),
+ bounded_Y=evolve(position.bounded_Y, value=Y),
+ bounded_Z=evolve(position.bounded_Z, value=Z))
+
+ def handle(self, type_metadata):
+ tol = 1e-5
+
+ block_format = type_metadata.block_format
+
+ is_lfe_channel = self.is_lfe_channel(type_metadata)
+
+ # try to find a speaker that matches a speakerLabel and type; earlier
+ # speakerLabel values have higher priority
+
+ for label in block_format.speakerLabel:
+ nominal_label = self.nominal_speaker_label(label)
+ if nominal_label in self.channel_names:
+ idx = self.channel_names.index(nominal_label)
+ if is_lfe_channel == self.is_lfe[idx]:
+ return self.pvs[idx]
+
+ # shift the nominal speaker position to the screen edges if specified
+ shifted_position = self.apply_screen_edge_lock(block_format.position)
+
+ # otherwise, find the closest speaker with the correct type within the given bounds
+
+ within_bounds = self.channels_within_bounds(shifted_position, tol)
+ if is_lfe_channel:
+ within_bounds &= self.is_lfe
+ else:
+ within_bounds &= ~self.is_lfe
+ if np.any(within_bounds):
+ closest = self.closest_channel_index(
+ shifted_position,
+ within_bounds,
+ tol)
+
+ # if we can uniquely identify the closes speaker, use it
+ if closest is not None:
+ return self.pvs[closest]
+
+ # otherwise, use the point source panner for non-LFE, and handle LFE
+ # channels using downmixing rules
+
+ if is_lfe_channel:
+ # if there are no LFE outputs, LFE channels are thrown away (as
+ # in bs.775). for 22.2 -> 5.1, according to "Downmixing Method
+ # for 22.2 Multichannel Sound Signal in 8K Super Hi-Vision
+ # Broadcasting", both LFE channels should be mixed into the one
+ # output channel, so any LFE channels that don't have a
+ # corresponding output (handled above) are sent to LFE1.
+ if "LFE1" in self.channel_names:
+ return self.pvs[self.channel_names.index("LFE1")]
+ else:
+ return np.zeros(self.n_channels)
+ else:
+ position = shifted_position.as_cartesian_array()
+
+ pv = np.zeros(self.n_channels)
+ pv[~self.is_lfe] = self.psp.handle(position)
+ return pv
diff --git a/ear/core/direct_speakers/renderer.py b/ear/core/direct_speakers/renderer.py
new file mode 100644
index 00000000..88a35e8d
--- /dev/null
+++ b/ear/core/direct_speakers/renderer.py
@@ -0,0 +1,86 @@
+import numpy as np
+from .panner import DirectSpeakersPanner
+from ..renderer_common import BlockProcessingChannel, InterpretTimingMetadata, FixedGains
+
+
+class InterpretDirectSpeakersMetadata(InterpretTimingMetadata):
+ """Interpret a sequence of DirectSpeakersTypeMetadata, producing a sequence of ProcessingBlock.
+
+ Args:
+ calc_gains (callable): Called with DirectSpeakersTypeMetadata to calculate per-channel gains.
+ """
+
+ def __init__(self, calc_gains):
+ super(InterpretDirectSpeakersMetadata, self).__init__()
+ self.calc_gains = calc_gains
+
+ def __call__(self, sample_rate, block):
+ """Yield ProcessingBlock that apply the processing for a given DirectSpeakersTypeMetadata.
+
+ Args:
+ sample_rate (int): Sample rate to operate in.
+ block (DirectSpeakersTypeMetadata): Metadata to interpret.
+
+ Yields:
+ One ProcessingBlock object that apply gains for a single input channel.
+ """
+ start_time, end_time = self.block_start_end(block)
+
+ start_sample = sample_rate * start_time
+ end_sample = sample_rate * end_time
+
+ gains = self.calc_gains(block)
+
+ yield FixedGains(start_sample, end_sample, gains)
+
+
+class DirectSpeakersRenderer(object):
+
+ options = DirectSpeakersPanner.options
+
+ @options.with_defaults
+ def __init__(self, layout, **options):
+ self._panner = DirectSpeakersPanner(layout, **options)
+ self._nchannels = len(layout.channels)
+
+ # tuples of an input channel number and a BlockProcessingChannel to apply to that
+ # input channel.
+ self.block_processing_channels = []
+
+ def set_rendering_items(self, rendering_items):
+ """Set the rendering items to process.
+
+ Note:
+ Since this resets the internal state, this should normally be called
+ once before rendering is started. Dynamic modification of the
+ rendering items could be implemented though another API.
+
+ Args:
+ rendering_items (list of DirectSpeakersRenderingItem): Items to process.
+ """
+ self.block_processing_channels = [(item.track_index,
+ BlockProcessingChannel(
+ item.metadata_source,
+ InterpretDirectSpeakersMetadata(self._panner.handle)))
+ for item in rendering_items]
+
+ def render(self, sample_rate, start_sample, input_samples):
+ """Process n input samples to produce n output samples.
+
+ Args:
+ sample_rate (int): Sample rate.
+ start_sample (int): Index of the first sample in input_samples.
+ input_samples (ndarray of (k, k) float): Multi-channel input sample
+ block; there must be at least as many channels as referenced in the
+ rendering items.
+
+ Returns:
+ (ndarray of (n, l) float): l channels of output samples
+ corresponding to the l loudspeakers in layout.
+ """
+ output_samples = np.zeros((len(input_samples), self._nchannels))
+
+ for channel_no, block_processing in self.block_processing_channels:
+ block_processing.process(sample_rate, start_sample, input_samples[:, channel_no], output_samples)
+
+ return output_samples
diff --git a/ear/core/direct_speakers/test/__init__.py b/ear/core/direct_speakers/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/core/direct_speakers/test/test_panner.py b/ear/core/direct_speakers/test/test_panner.py
new file mode 100644
index 00000000..1d4f5f72
--- /dev/null
+++ b/ear/core/direct_speakers/test/test_panner.py
@@ -0,0 +1,308 @@
+import numpy as np
+import numpy.testing as npt
+import pytest
+from ..panner import DirectSpeakersPanner
+from ...metadata_input import DirectSpeakersTypeMetadata, ExtraData
+from ....fileio.adm.elements import AudioBlockFormatDirectSpeakers, BoundCoordinate, Frequency, ScreenEdgeLock
+from ....fileio.adm.elements import DirectSpeakerCartesianPosition, DirectSpeakerPolarPosition
+from ... import bs2051
+from ...geom import cart
+
+
+def tm_with_labels(labels, lfe_freq=False):
+ """Get a DirectSpeakersTypeMetadata with the given speakerLabels and
+ default position."""
+ return DirectSpeakersTypeMetadata(
+ block_format=AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(0.0),
+ bounded_elevation=BoundCoordinate(0.0),
+ bounded_distance=BoundCoordinate(1.0),
+ ),
+ speakerLabel=labels),
+ extra_data=ExtraData(
+ channel_frequency=Frequency(
+ lowPass=120.0 if lfe_freq else None))
+ )
+
+
+def direct_pv(layout, channel):
+ pv = np.zeros(len(layout.channels))
+ pv[layout.channel_names.index(channel)] = 1.0
+ return pv
+
+
+def psp_pv(panner, position):
+ pv = np.zeros(len(panner.layout.channels))
+ pv[~panner.layout.is_lfe] = panner.psp.handle(position)
+ return pv
+
+
+def test_speaker_label():
+ layout = bs2051.get_layout("4+5+0")
+ p = DirectSpeakersPanner(layout)
+
+ for prefix in ["", "urn:itu:bs:2051:0:speaker:", "urn:itu:bs:2051:1:speaker:"]:
+ # normal case
+ npt.assert_allclose(p.handle(tm_with_labels([prefix + "M+000"])), direct_pv(layout, "M+000"))
+ npt.assert_allclose(p.handle(tm_with_labels([prefix + "M+030"])), direct_pv(layout, "M+030"))
+
+ # missing channels are ignored
+ npt.assert_allclose(p.handle(tm_with_labels([prefix + "M+030", prefix + "B+000"])), direct_pv(layout, "M+030"))
+ npt.assert_allclose(p.handle(tm_with_labels([prefix + "B+000", prefix + "M+030"])), direct_pv(layout, "M+030"))
+
+ # matching more than one channel should pick the first
+ npt.assert_allclose(p.handle(tm_with_labels([prefix + "M+000", prefix + "M+030"])), direct_pv(layout, "M+000"))
+ npt.assert_allclose(p.handle(tm_with_labels([prefix + "M+030", prefix + "M+000"])), direct_pv(layout, "M+030"))
+
+
+def test_speaker_label_additional_substitutions():
+ layout = bs2051.get_layout("4+5+0")
+ p = DirectSpeakersPanner(layout, additional_substitutions={"foo": "M+030"})
+
+ npt.assert_allclose(p.handle(tm_with_labels(["foo"])), direct_pv(layout, "M+030"))
+
+
+def test_lfe():
+ """Check the various LFE labelling options."""
+ layout = bs2051.get_layout("9+10+3")
+ p = DirectSpeakersPanner(layout)
+
+ # using frequency element and labels
+ for lfe_option in ["LFE", "LFE1", "LFEL"]:
+ npt.assert_allclose(p.handle(tm_with_labels([lfe_option], lfe_freq=True)), direct_pv(layout, "LFE1"))
+ for lfe_option in ["LFE2", "LFER"]:
+ npt.assert_allclose(p.handle(tm_with_labels([lfe_option], lfe_freq=True)), direct_pv(layout, "LFE2"))
+
+ # using just frequency element
+ npt.assert_allclose(p.handle(tm_with_labels([], lfe_freq=True)), direct_pv(layout, "LFE1"))
+
+ # check warnings with mismatch between label and frequency elements
+ with pytest.warns(None) as record:
+ # using just labels
+ for lfe_option in ["LFE", "LFE1", "LFEL"]:
+ npt.assert_allclose(p.handle(tm_with_labels([lfe_option])), direct_pv(layout, "LFE1"))
+ for lfe_option in ["LFE2", "LFER"]:
+ npt.assert_allclose(p.handle(tm_with_labels([lfe_option])), direct_pv(layout, "LFE2"))
+
+ # frequency element with incorrect label
+ npt.assert_allclose(p.handle(tm_with_labels(["M+000"], lfe_freq=True)), direct_pv(layout, "LFE1"))
+
+ assert len(record) == 6 and all(str(w.message) == "LFE indication from frequency element does not match speakerLabel."
+ for w in record)
+
+
+def test_one_lfe_out():
+ """When there is only one LFE output, both LFE1 and LFE2 should be sent to it."""
+ layout = bs2051.get_layout("4+5+0")
+ p = DirectSpeakersPanner(layout)
+
+ npt.assert_allclose(p.handle(tm_with_labels(["LFE1"], lfe_freq=True)), direct_pv(layout, "LFE1"))
+ npt.assert_allclose(p.handle(tm_with_labels(["LFE2"], lfe_freq=True)), direct_pv(layout, "LFE1"))
+
+
+def test_no_lfe_out():
+ """When there is no LFE output, both LFE1 and LFE2 should be discarded."""
+ layout = bs2051.get_layout("0+2+0")
+ p = DirectSpeakersPanner(layout)
+
+ npt.assert_allclose(p.handle(tm_with_labels(["LFE1"], lfe_freq=True)), np.zeros(len(layout.channels)))
+ npt.assert_allclose(p.handle(tm_with_labels(["LFE2"], lfe_freq=True)), np.zeros(len(layout.channels)))
+
+
+def test_dist_bounds_polar():
+ layout = bs2051.get_layout("9+10+3")
+ p = DirectSpeakersPanner(layout)
+ idx = layout.channel_names.index
+
+ # test horizontal bounds
+
+ expected_fr = np.zeros(len(layout.channels))
+ expected_fr[[idx("M+000"), idx("M+030")]] = [np.sqrt(0.5), np.sqrt(0.5)]
+
+ # no bounds, and position not on speaker -> use psp
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(15.0),
+ bounded_elevation=BoundCoordinate(0.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), expected_fr)
+
+ # lower bound expanded to speaker -> direct on that speaker
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(15.0, min=0.0),
+ bounded_elevation=BoundCoordinate(0.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M+000"))
+
+ # upper bound expanded to speaker -> direct on that speaker
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(15.0, max=30.0),
+ bounded_elevation=BoundCoordinate(0.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M+030"))
+
+ # upper and lower bounds expanded to speakers -> use psp again
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(15.0, min=0.0, max=30.0),
+ bounded_elevation=BoundCoordinate(0.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), expected_fr)
+
+ # closer to one than the other -> direct to that speaker
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(14.0, min=0.0, max=30.0),
+ bounded_elevation=BoundCoordinate(0.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M+000"))
+
+ # test vertical bounds
+
+ expected_mu = np.zeros(len(layout.channels))
+ expected_mu[[idx("M+000"), idx("U+000")]] = [np.sqrt(0.5), np.sqrt(0.5)]
+
+ # no bounds, and position not on speaker -> use psp
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(0.0),
+ bounded_elevation=BoundCoordinate(15.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), expected_mu)
+
+ # lower bound expanded to speaker -> direct on that speaker
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(0.0),
+ bounded_elevation=BoundCoordinate(15.0, min=0.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M+000"))
+
+ # upper bound expanded to speaker -> direct on that speaker
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(0.0),
+ bounded_elevation=BoundCoordinate(15.0, max=30.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "U+000"))
+
+ # upper and lower bounds expanded to speakers -> use psp again
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(0.0),
+ bounded_elevation=BoundCoordinate(15.0, min=0.0, max=30.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), expected_mu)
+
+ # closer to one than the other -> direct to that speaker
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(0.0),
+ bounded_elevation=BoundCoordinate(14.0, min=0.0, max=30.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M+000"))
+
+ # check that pole speakers are used even if 0 is excluded by azimuth range
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(15.0, 10.0, 20.0),
+ bounded_elevation=BoundCoordinate(90.0)))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "T+000"))
+
+
+def test_dist_bounds_cart():
+ layout = bs2051.get_layout("9+10+3")
+ p = DirectSpeakersPanner(layout)
+
+ # on speaker -> direct
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(1.0),
+ bounded_Y=BoundCoordinate(0.0),
+ bounded_Z=BoundCoordinate(0.0),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M-090"))
+
+ # lower bound on speaker -> direct
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(1.0),
+ bounded_Y=BoundCoordinate(0.1, min=0.0),
+ bounded_Z=BoundCoordinate(0.0),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M-090"))
+
+ # upper bound on speaker -> direct
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(1.0),
+ bounded_Y=BoundCoordinate(-0.1, max=0.0),
+ bounded_Z=BoundCoordinate(0.0),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M-090"))
+
+ # pick closest within bound
+ pos_30 = cart(30, 0, 1)
+ pos_5 = cart(5, 0, 1)
+ pos_25 = cart(25, 0, 1)
+
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(pos_5[0], min=pos_30[0], max=-pos_30[0]),
+ bounded_Y=BoundCoordinate(pos_5[1], min=pos_30[1], max=1.0),
+ bounded_Z=BoundCoordinate(0.0),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M+000"))
+
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(pos_25[0], min=pos_30[0], max=-pos_30[0]),
+ bounded_Y=BoundCoordinate(pos_25[1], min=pos_30[1], max=1.0),
+ bounded_Z=BoundCoordinate(0.0),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)), direct_pv(layout, "M+030"))
+
+
+def test_screen_edge_lock_polar():
+ layout = bs2051.get_layout("4+5+0")
+ p = DirectSpeakersPanner(layout)
+
+ # no bound set -> use psp
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(-30.0),
+ bounded_elevation=BoundCoordinate(0.0),
+ screenEdgeLock=ScreenEdgeLock(horizontal="right"),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)),
+ psp_pv(p, cart(-29, 0, 1)))
+
+ # bound set -> find closest to screen edge
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(0.0, min=-45.0, max=10.0),
+ bounded_elevation=BoundCoordinate(0.0),
+ screenEdgeLock=ScreenEdgeLock(horizontal="right"),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)),
+ direct_pv(layout, "M-030"))
+
+
+def test_screen_edge_lock_cart():
+ layout = bs2051.get_layout("4+5+0")
+ p = DirectSpeakersPanner(layout)
+
+ # no bound set -> use psp
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(0.0),
+ bounded_Y=BoundCoordinate(1.0),
+ bounded_Z=BoundCoordinate(0.0),
+ screenEdgeLock=ScreenEdgeLock(horizontal="right"),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)),
+ psp_pv(p, cart(-29, 0, 1)))
+
+ # bound set -> find closest to screen edge
+ bf = AudioBlockFormatDirectSpeakers(
+ position=DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(0.0, min=-0.1, max=1.0),
+ bounded_Y=BoundCoordinate(1.0, min=0.5),
+ bounded_Z=BoundCoordinate(0.0),
+ screenEdgeLock=ScreenEdgeLock(horizontal="right"),
+ ))
+ npt.assert_allclose(p.handle(DirectSpeakersTypeMetadata(bf)),
+ direct_pv(layout, "M-030"))
diff --git a/ear/core/geom.py b/ear/core/geom.py
new file mode 100644
index 00000000..6f415261
--- /dev/null
+++ b/ear/core/geom.py
@@ -0,0 +1,109 @@
+import numpy as np
+from ..common import CartesianPosition, PolarPosition, cart, azimuth, elevation, distance # noqa: F401
+
+
+def relative_angle(x, y):
+ """Assuming y is clockwise from x, increment y by 360 until it's not less
+ than x.
+
+ Parameters:
+ x (float): start angle in degrees.
+ y (float): end angle in degrees.
+
+ Returns:
+ float: y shifted such that it represents the same angle but is greater
+ than x.
+ """
+ while y - 360.0 >= x:
+ y -= 360.0
+ while y < x:
+ y += 360.0
+ return y
+
+
+def inside_angle_range(x, start, end, tol=0.0):
+ """Assuming end is clockwise from start, is the angle x inside [start,end]
+ within some tolerance?
+
+ Parameters:
+ x (float): angle to test, in degrees
+ start (float): start angle of range, in degrees
+ end (float): end angle of range, in degrees
+ tol (float): tolerance in degrees to check within
+
+ Returns:
+ bool
+ """
+ # end is clockwise from start; if end is start + 360, this rotation is
+ # preserved; this makes sure that a range of (-180, 180) or (0, 360) means
+ # any angle, while (-180, -180) or (0, 0) means a single angle, even though
+ # -180/180 and 0/360 are nominally the same angle
+ while end - 360.0 > start:
+ end -= 360.0
+ while end < start:
+ end += 360.0
+
+ # assume that x is clockwise from start - tol; if x is exactly
+ # start-tol+360, this is resolved to start-tol, so that the comparison with
+ # start-tol is >= rather than >
+ start_tol = start - tol
+ while x - 360.0 >= start_tol:
+ x -= 360.0
+ while x < start_tol:
+ x += 360.0
+
+ # x is greater than equal to start-tol, so we only need to compare against
+ # the end.
+ return x <= end + tol
+
+
+def ngon_vertex_order(vertices):
+ """Order the vertices of a convex, approximately planar polygon.
+
+ Parameters:
+ vertices (np.array of shape (n, 3)): Vertices to order
+
+ Returns:
+ integer np.array of shape (n,): Index into vertices, such that
+ vertices[ret] puts vertices into the right order; this behaviour is
+ similar to np.argsort.
+
+ Examples:
+ >>> ngon_vertex_order(np.array([[-1, 1, 0], [1.1, 1, 0],
+ ... [-1, 1, 1], [1, 1, 1]]))
+ array([1, 3, 2, 0])
+ """
+ centre = np.mean(vertices, axis=0)
+
+ # Pick two vertices to form a plane (with the third point being the
+ # origin); the vertices are ordered by the angles of points projected onto
+ # this plane. The first is picked arbitrarily, the second is picked to
+ # minimise the colinearity with the first.
+ a = vertices[0] - centre
+ b = min(vertices[1:] - centre, key=lambda vertex: np.abs(np.dot(vertex, a)))
+ # These vectors are neither normalised or orthogonal, so the projection
+ # onto them produces a linear transformation from the projection onto the
+ # plane (relative to the origin); this is fine, as affine transformations
+ # preserve straight lines.
+
+ # find the angle of the projection of each vertex onto the plane
+ vertices_rel_centre = vertices - centre[np.newaxis]
+ vertex_angles = np.arctan2(np.dot(vertices_rel_centre, a),
+ np.dot(vertices_rel_centre, b))
+
+ return np.argsort(vertex_angles)
+
+
+def local_coordinate_system(az, el):
+ """Vectors pointing along x, y and z, rotated so that +y points at cart(az, el, 1).
+
+ Parameters:
+ az (float): ADM format azimuth
+ el (float): ADM format elevation
+
+ Returns:
+ ndarray of shape (3, 3)
+ """
+ return cart([az - 90.0, az, az],
+ [0.0, el, el + 90.0],
+ 1.0)
diff --git a/ear/core/hoa.py b/ear/core/hoa.py
new file mode 100644
index 00000000..967f8664
--- /dev/null
+++ b/ear/core/hoa.py
@@ -0,0 +1,226 @@
+from __future__ import division
+import numpy as np
+import scipy.special
+from scipy.special import eval_legendre, legendre
+from scipy.optimize import fsolve
+
+
+def fact(n):
+ """Exact factorial function."""
+ return scipy.special.factorial(n, exact=True).astype(float)
+
+
+def Alegendre(n, m, x):
+ """Associated Legendre function P_n^m(x), ommitting the the (-1)^m
+ Condon-Shortley phase term."""
+ return (-1.0)**m * scipy.special.lpmv(m, n, x)
+
+
+def norm_N3D(n, abs_m):
+ """N3D normalisation for order n and degree m."""
+ return np.sqrt((2.0*n + 1.0) * fact(n-abs_m) / fact(n+abs_m))
+
+
+def norm_SN3D(n, abs_m):
+ """SN3D normalisation for order n and degree m."""
+ return np.sqrt(fact(n-abs_m) / fact(n+abs_m))
+
+
+def norm_FuMa(n, abs_m):
+ """FuMa normalisation for order n and degree m."""
+ if np.any(n > 3):
+ raise ValueError("The FuMa normalization is only defined up to order 3, not {order}.".format(order=np.max(n)))
+
+ convert = {(0, 0): 1/np.sqrt(2),
+ (1, 0): 1,
+ (1, 1): 1,
+ (2, 0): 1,
+ (2, 1): 2/np.sqrt(3),
+ (2, 2): 2/np.sqrt(3),
+ (3, 0): 1,
+ (3, 1): np.sqrt(45/32),
+ (3, 2): 3/np.sqrt(5),
+ (3, 3): np.sqrt(8/5)}
+ conv_factor = np.apply_along_axis(lambda a: convert[tuple(a)],
+ 0, [n, abs_m])
+
+ return norm_SN3D(n, abs_m) * conv_factor
+
+
+norm_functions = dict(
+ FuMa=norm_FuMa,
+ N3D=norm_N3D,
+ SN3D=norm_SN3D,
+)
+
+
+def sph_harm(n, m, az, el, norm=norm_SN3D):
+ """Spherical harmonic function Y_n^m(ax, el)."""
+ n, m, az, el = np.broadcast_arrays(n, m, az, el)
+ scale = np.ones_like(m, dtype=float)
+ select = m > 0
+ scale[select] = np.sqrt(2) * np.cos(m[select] * az[select])
+ select = m < 0
+ scale[select] = -np.sqrt(2) * np.sin(m[select] * az[select])
+
+ # in the spec, this is cos(el), where elevation is measured downwards
+ # from the top of the coordinate system; el here is from the centre.
+ return norm(n, np.abs(m)) * Alegendre(n, np.abs(m), np.sin(el)) * scale
+
+
+def to_acn(n, m):
+ """Ambisonics Channel Number for order n and degree m."""
+ return n*n + n + m
+
+
+def from_acn(acn):
+ """Get the order n and degree m from a given Ambisonics Channel Number."""
+ n = np.sqrt(acn).astype(int)
+ m = acn - n*n - n
+ return n, m
+
+
+def allrad_calc_G_virt(points, panning_func):
+ """See allrad_design."""
+ return np.apply_along_axis(panning_func, 1, points).T
+
+
+def allrad_design(points, panning_func, n, m, norm=norm_SN3D, G_virt=None):
+ """Decoder matrix design using the AllRAD[0] technique.
+
+ Parameters:
+ points (ndarray of (k, 3)): k virtual loudspeaker positions, from a
+ spherical t-design
+ panning_func (callable): function mapping from a cartesian position (as
+ an ndarray of (3,)) to a vector of l loudspeaker gains (as an ndarray
+ of (l,))
+ n (ndarray of (c,) integers): order for each input channel
+ m (ndarray of (c,) integers): degree for each input channel
+ norm (calable): normalisation function passed to sph_harm
+ G_virt: result of calling allrad_calc_G_virt(points, panning_func); can be
+ used to speed up creating multiple designs with the same loudspeaker
+ layout.
+
+ Returns:
+ ndarray of (l, c): decoder matrix
+
+ [0] F. Zotter and M. Frank, "All-round ambisonic panning and decoding,"
+ Journal of the audio engineering society, vol. 60, no. 10, pp. 807-820, 2012.
+ http://www.aes.org/e-lib/browse.cfm?elib=16554
+ """
+ az = -np.arctan2(points[:, 0], points[:, 1])
+ el = np.arctan2(points[:, 2], np.hypot(points[:, 0], points[:, 1]))
+
+ Y_virt = sph_harm(n[:, np.newaxis], m[:, np.newaxis], az[np.newaxis], el[np.newaxis], norm=norm_N3D)
+
+ D_virt = Y_virt.T / len(points)
+
+ if G_virt is None:
+ G_virt = allrad_calc_G_virt(points, panning_func)
+ D = np.dot(G_virt, D_virt)
+
+ # weight the resulting matrix by "Compensation" value to avoid the panning
+ # error (Loss of energy introduced by the panning)
+ D *= np.sqrt(len(points)) / np.linalg.norm(np.dot(D, Y_virt))
+
+ D *= norm_N3D(n, np.abs(m)) / norm(n, np.abs(m))
+
+ return D
+
+
+def load_points(fname="data/Design_5200_100_random.dat"):
+ """Load a spherical t-design from a file."""
+ # see data/README.md
+ import pkg_resources
+ with pkg_resources.resource_stream(__name__, fname) as points_file:
+ data = np.loadtxt(points_file)
+
+ if data.shape[1] == 2:
+ phi, theta = data.T
+ return np.array([
+ np.sin(theta) * np.cos(phi),
+ np.sin(theta) * np.sin(phi),
+ np.cos(theta),
+ ]).T
+ elif data.shape[1] == 3:
+ return data
+ else:
+ assert False
+
+
+def HankSph(n, kr):
+ """Evaluate the spherical Hankel function for order n and vector kr"""
+ return scipy.special.spherical_jn(n, kr) - 1j*scipy.special.spherical_yn(n, kr)
+
+
+def F(n, kr):
+ """Evaluate the filters used in the computation of the NFC filters for
+ order n and vector kr
+ """
+ return 1j**(-n) * HankSph(n, kr) / HankSph(0, kr)
+
+
+def H(n, r1, r2, k):
+ """Evaluate the NFC filters for order n, reference distance r1, restitution
+ distance r2, and vector k
+ """
+ return F(n, k * r1) / F(n, k * r2)
+
+
+def FreqRespH(n, r1, r2, k):
+ """Compute the "gain-less" NFC filter. Indeed, experience shows that the
+ phase-only NFC filter provides better results.
+ """
+ h = H(n, r1, r2, k)
+ return h / np.abs(h)
+
+
+def WindowMethod(Filter, NbPointsFFT, NbPointsFilter):
+ """Compute the Impulse Response of the filter "Filter" by computing its
+ IFFT on NbPointsFFT points, and then apply a "NbPointsFilter" points Tukey
+ window to reduce the filter length and avoid the Gibbs oscillations.
+ """
+ ImpResp = np.fft.irfft(Filter, NbPointsFFT) # IFFT to compute the impulse response of the filter ("Filter" contains the frequency response)
+ ImpRespShifted = np.fft.fftshift(ImpResp) # We shift the impulse response to bring all the energy together
+ # Window = tukey(NbPointsFilter) #Computation of the Tukey window
+ # Multiplication of the window with the impulse response.
+ ImpRespFinal = ImpRespShifted[len(ImpResp)//2-NbPointsFilter//2:len(ImpResp)//2+NbPointsFilter//2] # *Window
+ return ImpRespFinal
+
+
+def MultipleImpResp(Orders, r1, r2, Fs):
+ """Compute NFC filters for orders vector n, reference distance r1,
+ restitution distance r2, and sampling frequency Fs.
+ """
+ NbPointsFilter = 1024 # After looking at several impulse response of NFC filters, we decided to only keep 1024 points.
+ NbPointsFFT = 2**15
+ # Sampling indices. The sampling do not begin at 0 because it causes issues dues to the limit of the filters in 0.
+ x = np.concatenate(([1], np.linspace(1, Fs/2, NbPointsFFT//2)))
+ k = 2*np.pi*x/340.0 # Computation of the wavenumber
+
+ # Computation of the NFC filters for several orders contained in the n vector, weighted by a Tukey window.
+ filter_for_order = np.array([WindowMethod(FreqRespH(order, r1, r2, k),
+ NbPointsFFT, NbPointsFilter)
+ for order in range(max(Orders) + 1)])
+ return filter_for_order[Orders]
+
+
+def MaxRECoefficients(Nmax):
+ """rE computation (maximum zero of the Nmax+1 degree legendre polynomial)"""
+ t = np.arange(0.5, 1.0, 0.05) # Sampling the interval [0.5,1]
+ # Search the highest root of the N+1 degree legendre polynom in the interval [0.5,1]. This value is the highest rE reachable.
+ rE = np.max(fsolve(legendre(Nmax+1), t))
+
+ # The coefficient we need to apply to the n order HOA signals is just the n order legendre polynom evaluate at the value rE.
+ return eval_legendre(np.arange(Nmax + 1), rE)
+
+
+def ApproxMaxRECoefficients(Nmax):
+ """Approximate maxRE coefficients for a given order, from [0].
+
+ [0] Zotter, Franz, and Matthias Frank. "All-round ambisonic panning and
+ decoding." Journal of the audio engineering society 60.10 (2012)
+ """
+ rE = np.cos(np.radians(137.9 / (Nmax + 1.51)))
+
+ return eval_legendre(np.arange(Nmax + 1), rE)
diff --git a/ear/core/importance.py b/ear/core/importance.py
new file mode 100644
index 00000000..b7e3fe16
--- /dev/null
+++ b/ear/core/importance.py
@@ -0,0 +1,98 @@
+from .metadata_input import MetadataSource, ObjectRenderingItem
+
+
+def filter_by_importance(rendering_items,
+ threshold=10):
+ """Remove rendering items with an importance below the threshold
+
+ This method is a generator that essentially combines
+ `mute_audioBlockFormat_by_importance`, `filter_audioPackFormat_by_importance`
+ and `filter_audioObject_by_importance`, in that order, all with the same
+ importance threshold.
+
+ Parameters:
+ rendering_items (iterable of RenderingItems): RenderingItems to filter
+ threshold (int): importance threshold
+
+ Yields: RenderingItem
+ """
+ f = mute_audioBlockFormat_by_importance(rendering_items, threshold)
+ f = filter_audioObject_by_importance(f, threshold)
+ f = filter_audioPackFormat_by_importance(f, threshold)
+ return f
+
+
+def filter_audioObject_by_importance(rendering_items, threshold):
+ """Remove rendering items with an audioObject importance below the threshold
+
+ This method is a generator that can be used to filter RenderingItems
+ based on the importance of their parent audioObject(s).
+
+ Parameters:
+ rendering_items (iterable of RenderingItems): RenderingItems to filter
+ threshold (int): importance threshold
+
+ Yields: RenderingItem
+ """
+ for item in rendering_items:
+ if item.importance.audio_object is None or item.importance.audio_object >= threshold:
+ yield item
+
+
+def filter_audioPackFormat_by_importance(rendering_items, threshold):
+ """Remove rendering items with an audioPackFormat importance below a threshold
+
+ This method is a generator that can be used to filter RenderingItems
+ based on the importance of their parent audioPackFormat(s).
+
+
+ Parameters:
+ rendering_items (iterable of RenderingItems): RenderingItems to filter
+ threshold (int): importance threshold
+
+ Yields: RenderingItem
+ """
+ for item in rendering_items:
+ if item.importance.audio_pack_format is None or item.importance.audio_pack_format >= threshold:
+ yield item
+
+
+class MetadataSourceImportanceFilter(MetadataSource):
+ """A Metadata source adapter to change block formats if their importance is below a given threshold.
+
+ 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 get_next_block(self):
+ block = self._adapted.get_next_block()
+ if block is None:
+ return None
+ if block.block_format.importance < self._threshold:
+ block.block_format.gain = 0
+ return block
+
+
+def mute_audioBlockFormat_by_importance(rendering_items, threshold):
+ """Adapt rendering items of type `ObjectRenderingItem` to emulate block format importance handling
+
+ This installs an `MetadataSourceImportanceFilter` with the given threshold
+
+ Parameters:
+ rendering_items (iterable of RenderingItems): RenderingItems to adapt
+ threshold (int): importance threshold
+
+ Yields: RenderingItem
+ """
+ for item in rendering_items:
+ if isinstance(item, ObjectRenderingItem):
+ item.metadata_source = MetadataSourceImportanceFilter(adapted_source=item.metadata_source, threshold=threshold)
+ yield item
diff --git a/ear/core/layout.py b/ear/core/layout.py
new file mode 100644
index 00000000..ba297460
--- /dev/null
+++ b/ear/core/layout.py
@@ -0,0 +1,379 @@
+from __future__ import print_function
+from attr import attrs, attrib, evolve, Factory
+from attr.validators import instance_of, optional
+import numpy as np
+import sys
+from .geom import CartesianPosition, PolarPosition, inside_angle_range
+from ..common import list_of, CartesianScreen, PolarScreen, default_screen
+
+
+def to_polar_position(pp):
+ """Conform pp from a tuple to a PolarPosition."""
+ if isinstance(pp, PolarPosition):
+ return pp
+ else:
+ return PolarPosition(*pp)
+
+
+def _print_warning(message):
+ """Print a warning to stderr."""
+ print(message, file=sys.stderr) # noqa
+
+
+@attrs(frozen=True, slots=True)
+class Channel(object):
+ """Representation of a channel, with a name, real and nominal positions,
+ allowed azimuth and elevation ranges, and an lfe flag.
+
+ Attributes:
+ name (str): Channel name.
+ polar_position (PolarPosition, or arguments for PolarPosition):
+ real speaker location
+ polar_nominal_position (PolarPosition, or arguments for PolarPosition):
+ nominal speaker location, defaults to polar_position
+ az_range (2-tuple of floats):
+ azimuth range in degrees; allowed range is interpreted as
+ starting at az_range[0], moving anticlockwise to az_range[1];
+ defaults to the azimuth of polar_nominal_position.
+ el_range (2-tuple of floats):
+ elevation range in degrees; allowed range is interpreted as
+ starting at el_range[0], moving up to el_range[1]; defaults to
+ the elevation of polar_nominal_position.
+ is_lfe (bool): is this an LFE channel?
+ """
+
+ name = attrib(convert=str)
+ polar_position = attrib(convert=to_polar_position)
+ polar_nominal_position = attrib(convert=to_polar_position,
+ default=Factory(lambda self: self.polar_position,
+ takes_self=True))
+ az_range = attrib(default=Factory(lambda self: (self.polar_nominal_position.azimuth,
+ self.polar_nominal_position.azimuth),
+ takes_self=True))
+ el_range = attrib(default=Factory(lambda self: (self.polar_nominal_position.elevation,
+ self.polar_nominal_position.elevation),
+ takes_self=True))
+ is_lfe = attrib(default=False, convert=bool)
+
+ @property
+ def position(self):
+ return self.polar_position.as_cartesian_array()
+
+ @property
+ def norm_position(self):
+ return self.polar_position.norm_position
+
+ @property
+ def nominal_position(self):
+ return self.polar_nominal_position.as_cartesian_array()
+
+ def check_position(self, callback=_print_warning):
+ """Call callback with an error message if the position is outside the
+ azimuth and elevation ranges.
+ """
+ if not inside_angle_range(self.polar_position.azimuth, *self.az_range):
+ callback("{name}: azimuth {azimuth} out of range {az_range}.".format(name=self.name,
+ azimuth=self.polar_position.azimuth,
+ az_range=self.az_range))
+ if not self.el_range[0] <= self.polar_position.elevation <= self.el_range[1]:
+ callback("{name}: elevation {elevation} out of range {el_range}.".format(name=self.name,
+ elevation=self.polar_position.elevation,
+ el_range=self.el_range))
+
+
+@attrs(frozen=True, slots=True)
+class Layout(object):
+ """Representation of a loudspeaker layout, with a name and a list of channels."""
+ name = attrib()
+ channels = attrib()
+ screen = attrib(validator=optional(instance_of((CartesianScreen, PolarScreen))),
+ default=default_screen)
+
+ @property
+ def positions(self):
+ """Channel positions as an (n, 3) numpy array."""
+ return np.array([channel.position for channel in self.channels])
+
+ @property
+ def norm_positions(self):
+ """Normalised channel positions as an (n, 3) numpy array."""
+ return np.array([channel.norm_position for channel in self.channels])
+
+ @property
+ def nominal_positions(self):
+ """Nominal channel positions as an (n, 3) numpy array."""
+ return np.array([channel.nominal_position for channel in self.channels])
+
+ @property
+ def without_lfe(self):
+ """The same layout, without LFE channels."""
+ return evolve(self, channels=[channel for channel in self.channels if not channel.is_lfe])
+
+ @property
+ def is_lfe(self):
+ """Bool array corresponding to channels that selects LFE channels."""
+ return np.array([channel.is_lfe for channel in self.channels])
+
+ @property
+ def channel_names(self):
+ """The channel names for each channel."""
+ return [channel.name for channel in self.channels]
+
+ @property
+ def channels_by_name(self):
+ """dict from channel name to Channel."""
+ return dict((channel.name, channel) for channel in self.channels)
+
+ def check_positions(self, callback=_print_warning):
+ """Call callback with error messages for any channel positions that are out of range."""
+ for channel in self.channels:
+ channel.check_position(callback=callback)
+
+ def with_speakers(self, speakers):
+ """Remap speaker positions to those in speakers, and produce an upmix
+ matrix to map from the channels in the layout to the channels in the
+ speaker list.
+
+ Parameters:
+ speakers (list of Speaker): list of speakers to map to.
+
+ Returns:
+ - A new Layout object with the same channels but with positions
+ matching those in speakers.
+ - An upmix matrix m, such that m.dot(x) will map values
+ corresponding to the channels in self.channels to the channel
+ numbers in speakers. This matrix may be missing entries or have
+ duplicate entries depending on the contents of speakers; use
+ check_upmix_matrix.
+ """
+ def find_speaker(name):
+ for speaker in speakers:
+ if name in speaker.names:
+ return speaker
+
+ out_channels = max(speaker.channel for speaker in speakers) + 1
+
+ new_channels = []
+ upmix_matrix = np.zeros((out_channels, len(self.channels)))
+
+ for i, channel in enumerate(self.channels):
+ matching_speaker = find_speaker(channel.name)
+
+ if matching_speaker is not None:
+ upmix_matrix[matching_speaker.channel, i] = matching_speaker.gain_linear
+
+ if matching_speaker.polar_position is not None:
+ channel = evolve(channel, polar_position=matching_speaker.polar_position)
+
+ new_channels.append(channel)
+
+ return evolve(self, channels=new_channels), upmix_matrix
+
+ def with_real_layout(self, real_layout):
+ """Incorporate information from a real layout.
+
+ Note: see with_speakers for information on speaker mapping
+
+ Parameters:
+ real_layout (RealLayout): real layout information to incorporate
+
+ Returns:
+ - A new Layout object with updated speaker positions and screen
+ information.
+ - An upmix matrix to map the loudspeakers to the correct channels
+ and apply gains.
+ """
+ if real_layout.speakers is not None:
+ new_layout, upmix_matrix = self.with_speakers(real_layout.speakers)
+ else:
+ new_layout, upmix_matrix = self, np.eye(len(self.channels))
+
+ return evolve(new_layout, screen=real_layout.screen), upmix_matrix
+
+ def check_upmix_matrix(self, upmix, callback=_print_warning):
+ """Call callback with error messages for any errors in an upmix matrix.
+
+ - each input channel should be routed to 1 output channel
+ - each output channel should be routed from 0 or 1 input channels
+ """
+ for channel, column in zip(self.channels, upmix.T):
+ num_outputs = np.count_nonzero(column)
+ if num_outputs == 0:
+ callback("Channel {name} not mapped to any output.".format(name=channel.name))
+ if num_outputs > 1:
+ outputs = list(np.nonzero(column)[0])
+ callback("Channel {name} mapped to multiple outputs: {outputs}.".format(name=channel.name,
+ outputs=outputs))
+
+ for speaker, row in enumerate(upmix):
+ num_channels = np.count_nonzero(row)
+ if num_channels > 1:
+ channel_names = [channel_name for channel_name, coeff
+ in zip(self.channel_names, row) if coeff != 0.0]
+ callback("Speaker idx {speaker} used by multiple channels: {channel_names}".format(speaker=speaker,
+ channel_names=channel_names))
+
+
+@attrs(frozen=True, slots=True)
+class Speaker(object):
+ """Representation of a real-world loudspeaker; an array of these represents the
+ data required to use the renderer in a given listening room.
+
+ Attributes:
+ channel: 0-based channel number
+ names: list of BS.2051 channel names this speaker should handle.
+ polar_position: a PolarPosition object, or None
+ gain_linear: linear gain to apply to this output channel
+ """
+ channel = attrib()
+ names = attrib()
+ polar_position = attrib(default=None)
+ gain_linear = attrib(default=1.0)
+
+
+@attrs(frozen=True, slots=True)
+class RealLayout(object):
+ """Representation of a complete listening environment, onto which a
+ standard layout will be mapped.
+
+ Attributes:
+ speakers: all speakers that could be used
+ screen: screen information to use for screen-related content
+ """
+ speakers = attrib(default=None, validator=optional(list_of(Speaker)))
+ screen = attrib(validator=optional(instance_of((CartesianScreen, PolarScreen))),
+ default=default_screen)
+
+
+def load_real_layout(fileobj):
+ """Load a real layout from a yaml file.
+
+ The format is either a list of objects representing speakers, or an object
+ with optional keys "speakers" (which contains a list of objects
+ representing speakers) and "screen" (which contains an object representing
+ the screen).
+
+ Objects representing speakers may have the following keys:
+
+ channel: 0-based channel number, required
+ names: list (or a single string) of BS.2051 channel names that this
+ speaker should handle, i.e. like "M+000" or ["U+180", "UH+180"]
+ position: optional associative array containing the real loudspeaker position, with keys:
+ az: anti-clockwise azimuth in degrees
+ el: elevation in degrees
+ r: radius in metres
+ gain_linear: optional linear gain to be applied to this channel
+
+ A polar screen may be represented with the following keys:
+
+ type: "polar", required
+ aspectRatio: aspect ratio of the screen
+ centrePosition: object representing the centre position of the screen:
+ az: anti-clockwise azimuth in degrees
+ el: elevation in degrees
+ r: radius in metres
+ widthAzimuth: width of the screen in degrees
+
+ A Cartesian screen may be represented with the following keys:
+
+ type: "cart", required
+ aspectRatio: aspect ratio of the screen
+ centrePosition: object representing the centre position of the screen
+ containing X, Y and Z coordinates
+ widthX: width of the screen along the Cartesian X axis
+
+ If the screen is omitted, the default screen is used; if the screen is
+ specified but null, then screen-related processing will not be applied.
+
+ Parameters:
+ file: a file-like object to read yaml from
+
+ Returns:
+ list of Speaker
+ """
+ from ruamel import yaml
+
+ def parse_yaml_polar_position(position):
+ if set(position.keys()) == set(["az", "el", "r"]):
+ return PolarPosition(position["az"], position["el"], position["r"])
+ else:
+ raise Exception("Unknown polar position format: {}".format(position))
+
+ def parse_yaml_cart_position(position):
+ if set(position.keys()) == set(["X", "Y", "Z"]):
+ return CartesianPosition(position["X"], position["Y"], position["Z"])
+ else:
+ raise Exception("Unknown Cartesian position format: {}".format(position))
+
+ def parse_yaml_speaker(yaml_speaker):
+ names = yaml_speaker["names"]
+ if not isinstance(names, list):
+ names = [names]
+
+ speaker = Speaker(channel=yaml_speaker["channel"],
+ names=names)
+
+ if "position" in yaml_speaker:
+ speaker = evolve(speaker,
+ polar_position=parse_yaml_polar_position(yaml_speaker["position"]))
+
+ if "gain_linear" in yaml_speaker:
+ speaker = evolve(speaker,
+ gain_linear=yaml_speaker["gain_linear"])
+
+ return speaker
+
+ def parse_yaml_screen(yaml_screen):
+ if yaml_screen is None:
+ return None
+
+ screen_type = yaml_screen["type"]
+
+ if screen_type == "polar":
+ return PolarScreen(
+ aspectRatio=float(yaml_screen["aspectRatio"]),
+ centrePosition=parse_yaml_polar_position(yaml_screen["centrePosition"]),
+ widthAzimuth=float(yaml_screen["widthAzimuth"]),
+ )
+ elif screen_type == "cart":
+ return CartesianScreen(
+ aspectRatio=float(yaml_screen["aspectRatio"]),
+ centrePosition=parse_yaml_cart_position(yaml_screen["centrePosition"]),
+ widthX=float(yaml_screen["widthX"]),
+ )
+ else:
+ raise Exception("Unknown screen type: {!r}".format(screen_type))
+
+ yaml_info = yaml.safe_load(fileobj)
+
+ if isinstance(yaml_info, dict):
+ yaml_info_dict = yaml_info
+ elif isinstance(yaml_info, list):
+ yaml_info_dict = dict(speakers=yaml_info)
+ else:
+ raise Exception("Expected mapping or list of loudspeakers.")
+
+ speakers = (list(map(parse_yaml_speaker, yaml_info_dict["speakers"]))
+ if "speakers" in yaml_info_dict
+ else None)
+
+ screen = (parse_yaml_screen(yaml_info_dict["screen"])
+ if "screen" in yaml_info_dict
+ else default_screen)
+
+ return RealLayout(speakers=speakers, screen=screen)
+
+
+def load_speakers(fileobj):
+ """Load a list of speakers from a yaml file.
+
+ This is a legacy wrapper around load_real_layout; see its documentation for
+ format info.
+
+ Parameters:
+ file: a file-like object to read yaml from
+
+ Returns:
+ list of Speaker
+ """
+ return load_real_layout(fileobj).speakers
diff --git a/ear/core/metadata_input.py b/ear/core/metadata_input.py
new file mode 100644
index 00000000..a1135853
--- /dev/null
+++ b/ear/core/metadata_input.py
@@ -0,0 +1,177 @@
+from attr import attrib, attrs, Factory
+from attr.validators import instance_of, optional
+from fractions import Fraction
+from ..common import list_of, default_screen
+from ..fileio.adm.elements import AudioBlockFormatObjects, AudioBlockFormatDirectSpeakers, Frequency
+
+
+class MetadataSource(object):
+ """A source of metadata for some input channels."""
+
+ def get_next_block(self):
+ """Get the next metadata block, if one is available.
+
+ Returns:
+ TypeMetadata
+ """
+ raise NotImplementedError()
+
+
+class MetadataSourceIter(MetadataSource):
+ """Metadata source that iterates through a list of TypeMetadata objects.
+
+ Args:
+ type_metadatas (list of TypeMetadata): List of metadata to iterate through.
+ """
+
+ def __init__(self, type_metadatas):
+ self.type_metadatas_iter = iter(type_metadatas)
+
+ def get_next_block(self):
+ return next(self.type_metadatas_iter, None)
+
+
+@attrs(slots=True)
+class TypeMetadata(object):
+ """Base class for *TypeMetadata classes; these should represent all the
+ parameters needed to render some set of audio channels within some time
+ bounds.
+ """
+
+
+@attrs(slots=True)
+class RenderingItem(object):
+ """Base class for *RenderingItem classes; these should represent an item to
+ be rendered, combining a MetadataSource that produces a sequence of
+ TypeMetadata objects, and some indices into the tracks that this metadata
+ applies to.
+ """
+
+
+@attrs(slots=True)
+class ExtraData(object):
+ """Common metadata from outside the ADM block format.
+
+ Attributes:
+ object_start (Fraction or None): Start time of audioObject.
+ object_duration (Fraction or None): Duration of audioObject.
+ reference_screen (CartesianScreen or PolarScreen): Reference screen from audioProgramme.
+ channel_frequency (Frequency): Frequency information from audioChannel.
+ """
+ object_start = attrib(validator=optional(instance_of(Fraction)), default=None)
+ 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))
+
+
+@attrs(slots=True)
+class ImportanceData(object):
+ """Importance metadata for a RenderingItem
+
+ Attributes:
+ audio_object (int or None): Importance that has been derived from the audioObject level
+ audio_pack_format (int or None): Importance that has been derived from the audioPackFormat level
+ """
+ audio_object = attrib(validator=optional(instance_of(int)), default=None)
+ audio_pack_format = attrib(validator=optional(instance_of(int)), default=None)
+
+
+#################################################
+# type metadata and rendering items for each type
+#################################################
+
+# objects
+
+@attrs(slots=True)
+class ObjectTypeMetadata(TypeMetadata):
+ """TypeMetadata for typeDefinition="Objects"
+
+ Attributes:
+ block_format (AudioBlockFormatObjects): Block format.
+ extra_data (ExtraData): Extra parameters from outside block format.
+ """
+ block_format = attrib(validator=instance_of(AudioBlockFormatObjects))
+ extra_data = attrib(validator=instance_of(ExtraData), default=Factory(ExtraData))
+
+
+@attrs(slots=True)
+class ObjectRenderingItem(RenderingItem):
+ """RenderingItem for typeDefinition="Objects"
+
+ Attributes:
+ track_index (int): Zero based input track index for this item.
+ metadata_source (MetadataSource): Source of ObjectTypeMetadata objects.
+ importance (ImportanceData): Importance values applicable for this item.
+ """
+ track_index = attrib(validator=instance_of(int))
+ metadata_source = attrib(validator=instance_of(MetadataSource))
+ importance = attrib(validator=instance_of(ImportanceData), default=Factory(ImportanceData))
+
+
+# direct speakers
+
+@attrs(slots=True)
+class DirectSpeakersTypeMetadata(TypeMetadata):
+ """TypeMetadata for typeDefinition="DirectSpeakers"
+
+ Attributes:
+ block_format (AudioBlockFormatDirectSpeakerss): Block format.
+ extra_data (ExtraData): Extra parameters from outside block format.
+ """
+ block_format = attrib(validator=instance_of(AudioBlockFormatDirectSpeakers))
+ extra_data = attrib(validator=instance_of(ExtraData), default=Factory(ExtraData))
+
+
+@attrs(slots=True)
+class DirectSpeakersRenderingItem(RenderingItem):
+ """RenderingItem for typeDefinition="DirectSpeakers"
+
+ Attributes:
+ track_index (int): Zero based input track index for this item.
+ metadata_source (MetadataSource): Source of DirectSpeakersTypeMetadata objects.
+ importance (ImportanceData): Importance values applicable for this item.
+ """
+ track_index = attrib(validator=instance_of(int))
+ metadata_source = attrib(validator=instance_of(MetadataSource))
+ importance = attrib(validator=instance_of(ImportanceData), default=Factory(ImportanceData))
+
+
+# HOA
+
+@attrs(slots=True)
+class HOATypeMetadata(TypeMetadata):
+ """TypeMetadata for typeDefinition="HOA"
+
+ Attributes:
+ orders (list of int): Order for each input channel.
+ degrees (list of int): Degree for each channel.
+ normalization (str): Normalization for all channels.
+ nfcRefDist (float or None): NFC Reference distance for all channels.
+ screenRef (bool): Are these channels screen related?
+ extra_data (ExtraData): Info from object and channels for all channels.
+ rtime (Fraction or None): Start time of block.
+ duration (Fraction or None): Duration of block.
+ """
+ orders = attrib(validator=list_of(int))
+ degrees = attrib(validator=list_of(int))
+ normalization = attrib()
+ nfcRefDist = attrib(validator=optional(instance_of(float)), default=None)
+ screenRef = attrib(validator=instance_of(bool), default=False)
+ extra_data = attrib(validator=instance_of(ExtraData), default=Factory(ExtraData))
+ rtime = attrib(default=None, validator=optional(instance_of(Fraction)))
+ duration = attrib(default=None, validator=optional(instance_of(Fraction)))
+
+
+@attrs(slots=True)
+class HOARenderingItem(RenderingItem):
+ """RenderingItem for typeDefinition="HOA"
+
+ Attributes:
+ track_indices (collection of int): Zero based index of each track in this item.
+ metadata_source (MetadataSource): Source of HOATypeMetadata objects;
+ will usually contain only one object.
+ importance (ImportanceData): Importance values applicable for this item.
+ """
+ track_indices = attrib()
+ metadata_source = attrib(validator=instance_of(MetadataSource))
+ importance = attrib(validator=instance_of(ImportanceData), default=Factory(ImportanceData))
diff --git a/ear/core/metadata_processing.py b/ear/core/metadata_processing.py
new file mode 100644
index 00000000..c95e01cf
--- /dev/null
+++ b/ear/core/metadata_processing.py
@@ -0,0 +1,20 @@
+from .importance import filter_by_importance
+
+
+def preprocess_rendering_items(rendering_items, importance_threshold=None):
+ """ Applies configurable preproccessing steps to rendering items.
+
+ Which preprocessing steps will be applied depends on the arguments given
+ to this function.
+ Each parameter instaniates a single preprocessing step.
+ If the paramter is None, the associated step will be skipped.
+
+ Parameters:
+ importance_threshold (int): Installs `importance.filter_by_importance`
+
+ Returns: List of RenderingItem
+ """
+ f = rendering_items
+ if importance_threshold is not None:
+ f = filter_by_importance(f, threshold=importance_threshold)
+ return list(f)
diff --git a/ear/core/monitor.py b/ear/core/monitor.py
new file mode 100644
index 00000000..f5a23c76
--- /dev/null
+++ b/ear/core/monitor.py
@@ -0,0 +1,37 @@
+import numpy as np
+import warnings
+
+
+class PeakMonitor(object):
+ """Monitor the peak level of each channel in a multichannel stream.
+
+ Call process(samples) on each block of samples, then print_warnings() to
+ warn about overloaded channels.
+
+ Parameters:
+ nchannels (int): number of channels to monitor
+ """
+
+ def __init__(self, nchannels):
+ self.peak_abs_linear = np.zeros(nchannels)
+
+ def process(self, samples):
+ """Process a block of samples.
+
+ Parameters:
+ samples (ndarray of (m, nchannels)): block of m samples
+ """
+ max_in_block = np.max(np.abs(samples), axis=0)
+ self.peak_abs_linear = np.maximum(self.peak_abs_linear, max_in_block)
+
+ def has_overloaded(self):
+ return np.any(self.peak_abs_linear > 1)
+
+ def warn_overloaded(self):
+ """Produce a warning for each overloaded channel."""
+ for channel, max_sample in enumerate(self.peak_abs_linear):
+ if max_sample > 1:
+ warnings.warn("overload in channel {channel}; peak level was {max_dbfs:.1f}dBFS".format(
+ channel=channel,
+ max_dbfs=20*np.log10(max_sample),
+ ))
diff --git a/ear/core/objectbased/__init__.py b/ear/core/objectbased/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/core/objectbased/decorrelate.py b/ear/core/objectbased/decorrelate.py
new file mode 100644
index 00000000..77c07ff3
--- /dev/null
+++ b/ear/core/objectbased/decorrelate.py
@@ -0,0 +1,80 @@
+from __future__ import division
+import numpy as np
+from ...options import Option, SubOptions, OptionsHandler
+
+
+def gen_rand_mt19937(seed, n):
+ # given the current numpy code, this results in raw 32 bit values from mt19937;
+ # the docs guarantee forward compatibility given a fixed seed so it should
+ # remain the same
+ return np.random.RandomState(seed).randint(0, 2**32, n, dtype=np.uint32)
+
+
+def gen_rand_float(seed, n):
+ return gen_rand_mt19937(seed, n) / 2**32
+
+
+basic_options = OptionsHandler(
+ size=Option(
+ default=512,
+ description="decorrelation filter length",
+ ),
+)
+
+
+@basic_options.with_defaults
+def design_decorrelator_basic(decorrelator_id, size):
+ """Design an all-pass random-phase FIR filter.
+
+ Parameters:
+ decorrelator_id (int): Random seed, to obtain different filters.
+ size (int): filter length.
+
+ Returns:
+ array of (size,): Filter coefficients.
+ """
+ rand = gen_rand_float(decorrelator_id, size//2 - 1)
+
+ phase = np.zeros(size // 2 + 1)
+ phase[1: size // 2] = 2 * np.pi * rand
+
+ freq = np.exp(1j * phase)
+
+ return np.fft.irfft(freq)
+
+
+design_methods = dict(
+ basic=(design_decorrelator_basic, basic_options),
+)
+
+
+design_options = OptionsHandler(
+ method=Option(
+ default="basic",
+ description="filter design method, one of: {}".format(", ".join(design_methods.keys())),
+ ),
+ **{
+ method + "_opts": SubOptions(handler=opts_handler,
+ description="options used for design method '{}'".format(method))
+ for method, (func, opts_handler) in design_methods.items()}
+)
+
+
+@design_options.with_defaults
+def design_decorrelators(layout, method, **options):
+ """Design one filter for each channel in layout.
+
+ Parameters:
+ layout (layout.Layout): Layout to design for; channel names are used to
+ allocate filters to channels.
+ **options: options for design_decorrelator
+
+ Returns:
+ array of shape (filt_len, nchannels): Decorrelation filters.
+ """
+ sorted_channel_names = sorted(layout.channel_names)
+
+ design = design_methods[method][0]
+ decorrelators = [design(sorted_channel_names.index(name), **options[method + "_opts"])
+ for name in layout.channel_names]
+ return np.array(decorrelators).T
diff --git a/ear/core/objectbased/extent.py b/ear/core/objectbased/extent.py
new file mode 100644
index 00000000..304d2363
--- /dev/null
+++ b/ear/core/objectbased/extent.py
@@ -0,0 +1,329 @@
+from attr import attrs, attrib
+import numpy as np
+from ..geom import cart, azimuth, elevation, local_coordinate_system
+from ..util import safe_norm_position
+
+
+class SpreadingPanner(object):
+ """A wrapper around another panner that pans using a uniform spread of
+ points around the sphere given a weighting function."""
+
+ def __init__(self, panning_func, n_rows):
+ """
+ Args:
+ panner: panner used to find panning values of virtual sources
+ n_rows (int): number of rows rows to place on sphere, e.g. 37 for 5 degree spacing
+ """
+ self.panning_func = panning_func
+ self.n_rows = n_rows
+ self.panning_positions = self.generate_panning_positions_even(n_rows)
+ self.panning_positions_results = np.apply_along_axis(panning_func, 1, self.panning_positions)
+
+ @classmethod
+ def generate_panning_positions_even(cls, n_rows):
+ """Generate points spread evenly on the sphere.
+ Based on http://web.archive.org/web/20150108040043/http://www.math.niu.edu/~rusin/known-math/95/equispace.elect
+
+ Args:
+ n_rows (int): number of rows rows to place on sphere, e.g. 37 for 5 degree spacing
+
+ Returns:
+ (n,3) cartesian array.
+ """
+ elevations = np.linspace(-90, 90, num=n_rows, endpoint=True)
+
+ positions = []
+
+ for el in elevations:
+ radius = np.cos(np.radians(el))
+ perimiter = 2 * np.pi * radius
+ perimiter_centre = 2 * np.pi
+
+ n_points = int(round((perimiter / perimiter_centre) * 2 * (n_rows - 1)))
+ if n_points == 0: n_points = 1
+
+ azimuths = np.linspace(0, 360, num=n_points, endpoint=False)
+ for az in azimuths:
+ positions.append(cart(az, el, 1))
+ return np.array(positions)
+
+ def panning_values_for_weight(self, weight_for_vec):
+ """Panning values for a given weighting function.
+
+ Args:
+ weight_for_vec: function from Cartesian position to weight in range (0, 1).
+ This must accept a np array of size (n, 3) for n
+ points (i.e. it must be vectorised)
+
+ Returns:
+ panning value for each speaker.
+ """
+ values_for_pos = weight_for_vec(self.panning_positions)
+ total_pv = np.dot(values_for_pos, self.panning_positions_results)
+ return total_pv / np.linalg.norm(total_pv)
+
+
+@attrs
+class ExtentPanner(object):
+ """Base class for extent panners that use a SpreadingPanner."""
+
+ panning_func = attrib()
+ n_rows = attrib(default=37)
+ spreading_panner = attrib()
+
+ @spreading_panner.default
+ def init_spreading_panner(self):
+ return SpreadingPanner(self.panning_func, self.n_rows)
+
+ @spreading_panner.validator
+ def validate_spreading_panner(self, attribute, value):
+ assert value.panning_func == self.panning_func
+ assert value.n_rows == self.n_rows
+
+
+class PolarExtentPanner(ExtentPanner):
+
+ fade_width = 10.0 # degrees
+
+ @classmethod
+ def get_weight_func(cls, position, width, height):
+ """Weighting function for spread sources.
+
+ The weighting function is one inside a region approximately determined
+ by a width x height rectangle in azimuth-elevation space, with
+ maximally-sized rounded corners; the shape of the corners is calculated
+ using the vector angle from their centres (always directly above or
+ below the source position) so as to avoid issues at the poles.
+
+ The two straight edges of the rectangle are always parallel in
+ Cartesian space; this is achieved by following azimuth lines; for tall
+ sources, the whole coordinate system is rotated 90 degrees about the
+ source position to achieve this.
+
+ Note that for sources where width == height, this degrades to a
+ circular region relative to the source position.
+
+ To make the two ends meet, the width is adjusted such that a width of
+ 180 degrees is mapped to width + height.
+
+ Parameters:
+ position (array of shape (3,)): Centre of the extent.
+ width (float): Width of the extent in degrees from one edge to the other.
+ height (float): Height of the extent in degrees from one edge to the other.
+
+ Returns:
+ weighting function from array of (n, 3) to (n)
+ """
+ width = np.radians(width) / 2
+ height = np.radians(height) / 2
+
+ # basis vectors to rotate the vsource positions towards position
+ basises = calc_basis(position)
+
+ circle_radius = min(width, height)
+
+ # Flip the width and the height such that it is always wider than it is
+ # high from here in.
+ if height > width:
+ width, height = height, width
+ flipped_basises = basises[[2, 1, 0]]
+ else:
+ flipped_basises = basises
+
+ # modify the width to make it meet at the back.
+ width_full = np.pi + height # width we'd need to make it meet at the back
+ # interpolate to this from a width of pi/2 to pi
+ width_mod = np.interp(width,
+ [0, np.pi/2, np.pi],
+ [0, np.pi/2, width_full])
+ # apply this fully for a height of less than pi/4; tail off until pi/2
+ width = np.interp(height,
+ [0, np.pi/4, np.pi/2, np.pi], # noqa
+ [width_mod, width_mod, width, width]) # noqa
+
+ # angle of the circle centres from the source position; width is to the end of the rectangle.
+ circle_pos = width - circle_radius
+
+ # Cartesian circle centres
+ circle_positions = cart_on_basis(flipped_basises,
+ np.array([-circle_pos, circle_pos]),
+ np.zeros(2))
+
+ def f(vsource_positions):
+ # Flipped azimuths and elevations; the straight edges are always along azimuth lines.
+ azimuths, elevations = azimuth_elevation_on_basis(flipped_basises, vsource_positions)
+
+ # The distance is the angle away from the defined shape; 0 or negative is inside.
+ distances = np.zeros(len(vsource_positions))
+
+ # for the straight lines
+ on_flat_part = abs(azimuths) <= circle_pos
+ distances[on_flat_part] = np.abs(elevations[on_flat_part]) - circle_radius
+
+ # distance from the closest circle centre
+ on_circle = ~on_flat_part
+ circle_distances = np.arccos(np.clip(np.dot(vsource_positions[on_circle], circle_positions.T), -1, 1))
+ distances[on_circle] = np.min(circle_distances, axis=1) - circle_radius
+
+ # fade the weight from one to zero over fade_width
+ return np.interp(distances, [0, np.radians(cls.fade_width)], [1, 0])
+
+ return f
+
+ @classmethod
+ def plot_weight_func(cls, weight_func, points):
+ import matplotlib.pyplot as plt
+ from mpl_toolkits.mplot3d import Axes3D # noqa
+
+ weights = weight_func(points)
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection='3d', aspect=1)
+
+ nonzero_points, nonzero_weights = points[weights > 0], weights[weights > 0]
+
+ colors = np.zeros((len(nonzero_points), 4))
+ colors[:, 0:3] = (nonzero_points + 1) / 2
+ colors[:, 0:3] /= np.linalg.norm(colors[:, 0:3], ord=1, axis=1, keepdims=True)
+ colors[:, 3] = nonzero_weights
+
+ ax.scatter(*nonzero_points.T, color=colors, depthshade=0)
+ ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1)
+ # fig.colorbar(plot, shrink=0.5, aspect=10)
+ plt.show()
+
+ def calc_pv_spread(self, position, width, height):
+ """Calculate the speaker panning values for the position, width, and
+ height of a source; this just deals with the positioning and spreading.
+ """
+ # When calculating the spread panning values the width and height are
+ # set to at least fade_width. For sizes where any of the dimensions is
+ # less than this, interpolate linearly between the point and spread
+ # panning values.
+ ammount_spread = np.interp(max(width, height), [0, self.fade_width], [0, 1])
+ ammount_point = 1.0 - ammount_spread
+
+ pv = 0.0
+ if ammount_point != 0:
+ pv += ammount_point * self.panning_func(position)
+ if ammount_spread != 0:
+ # minimum width and height as above
+ width = np.maximum(width, self.fade_width / 2)
+ height = np.maximum(height, self.fade_width / 2)
+
+ weight_f = self.get_weight_func(position, width, height)
+ pv += ammount_spread * self.spreading_panner.panning_values_for_weight(weight_f)
+
+ return pv
+
+
+class CartExtentPanner(ExtentPanner):
+
+ fade_width = 10.0 # degrees
+
+ @classmethod
+ def get_weight_func(cls, position, size):
+ """Weighting function based on ellipsoid-ray intersection.
+
+ The parameters define a spheroid whose centre is at `position`, and
+ whose axes lengths are given by `size`. The weight for a virtual source
+ positioned at v is the square of the distance that the ray vt for t > 0
+ is inside this ellipsoid. The squared distance is used to avoid high
+ rates of change for rays which pass through the edge of the ellipsoid.
+
+ Parameters:
+ position (array of shape (3,)): Ellipsoid centre position
+ size (array of shape (3,)): Ellipsoid axis lengths, the distance
+ from `position` to the surface along each axis.
+
+ Returns:
+ weighting function from array of (n, 3) to (n)
+ """
+
+ def f(vsource_positions):
+ # find t1 and t2, the two solutions for t in norm((vt - position) / r) == 1
+ # using the quadratic formula
+ a = np.sum((vsource_positions / size) ** 2, axis=1)
+ b = -2 * np.sum((vsource_positions / size) * (position / size), axis=1)
+ c = np.sum((position / size) ** 2)[np.newaxis] - 1
+
+ det = b**2 - 4 * a * c
+ intersection = det > 1e-6
+
+ # work on just the source positions which intersect the ellipsoid
+ a, b, det = a[intersection], b[intersection], det[intersection]
+
+ t1 = (-b - np.sqrt(det)) / (2 * a)
+ t2 = (-b + np.sqrt(det)) / (2 * a)
+
+ distances = np.maximum(t2, 0.0) - np.maximum(t1, 0.0)
+
+ all_distances = np.zeros(len(vsource_positions))
+ all_distances[intersection] = distances
+
+ return all_distances ** 2
+
+ return f
+
+ def calc_pv_spread(self, position, size):
+ # Input size is from edge to edge, but it's more convenient to operate
+ # on the distance from the centre
+ size /= 2.0
+
+ distance = np.linalg.norm(position)
+
+ # size increased so that it's safe to use with the extent panner
+ min_spread_size = 2.0 * np.tan(np.radians(5))
+ size_mod_for_spread = np.maximum(size, min_spread_size)
+
+ # size modified so that it's extended at the centre -- this is the real
+ # size that we want, which will be made by xfade between point source
+ # and extent. This is always <= size_mod_for_spread
+ size_mod_for_psp = np.array([np.interp(distance, (0, 1), (size_mod_for_spread_axis, size_axis))
+ for size_mod_for_spread_axis, size_axis in zip(size_mod_for_spread, size)])
+
+ # 0: all from psp, 1: all from spread
+ psp_xfade = np.interp(np.max(size_mod_for_psp), (0, min_spread_size), (0, 1))
+
+ pv = 0.0
+ if psp_xfade < (1-1e-6):
+ pv += (1 - psp_xfade) * self.panning_func(position)
+ if psp_xfade > 1e-6:
+ weight_f = self.get_weight_func(position, size_mod_for_spread)
+ pv += psp_xfade * self.spreading_panner.panning_values_for_weight(weight_f)
+
+ return pv / np.linalg.norm(pv)
+
+
+def calc_basis(source_pos):
+ """Calculate basis vectors that rotate (0, 1, 0) onto source_pos."""
+ source_pos = safe_norm_position(source_pos)
+ az, el = azimuth(source_pos), elevation(source_pos)
+
+ # points near the poles have indeterminate azimuth; assume 0
+ if np.abs(el) > 90 - 1e-5:
+ az = 0
+
+ return local_coordinate_system(az, el)
+
+
+def cart_on_basis(basis, az, el):
+ """Polar to Cartesian in radians with no distance, in a given basis."""
+ cart_pos_rel = np.array([
+ np.sin(az) * np.cos(el),
+ np.cos(az) * np.cos(el),
+ np.sin(el)]).T
+
+ return np.dot(cart_pos_rel, basis)
+
+
+def azimuth_elevation_on_basis(basis, vsource_pos):
+ """Cartesian to polar in radians, in a given basis."""
+ # project onto each basis
+ components = np.dot(vsource_pos, basis.T)
+
+ azimuth = np.arctan2(components[..., 0], # right
+ components[..., 1]) # forward
+ elevation = np.arcsin(components[..., 2]) # up
+
+ return azimuth, elevation
diff --git a/ear/core/objectbased/gain_calc.py b/ear/core/objectbased/gain_calc.py
new file mode 100644
index 00000000..30f36055
--- /dev/null
+++ b/ear/core/objectbased/gain_calc.py
@@ -0,0 +1,381 @@
+from collections import namedtuple
+import numpy as np
+import warnings
+from . import extent
+from .. import point_source
+from ...options import SubOptions, OptionsHandler
+from ..geom import azimuth, elevation, cart, inside_angle_range, local_coordinate_system
+from .zone import ZoneExclusionDownmix
+from ..subwoofer import lfe_downmix_matrix
+from ..renderer_common import is_lfe
+from ...fileio.adm.elements import CartesianZone, PolarZone, ObjectCartesianPosition, ObjectPolarPosition
+from ..screen_scale import ScreenScaleHandler
+from ..screen_edge_lock import ScreenEdgeLockHandler
+
+
+def cube_to_sphere(position):
+ """Warp from positions in a cube to positions in a sphere by scaling
+ dependant on the direction.
+
+ Parameters:
+ position (array of length 3): Cartesian position
+
+ Returns:
+ array of length 3: warped position
+ """
+ norm = np.linalg.norm(position)
+ if norm > 1e-10:
+ return position * (np.max(np.abs(position)) / norm)
+ else:
+ return position
+
+
+def sphere_to_cube(position):
+ """Warp from positions in a sphere to positions in a cube by scaling
+ dependant on the direction.
+
+ Parameters:
+ position (array of length 3): Cartesian position
+
+ Returns:
+ array of length 3: warped position
+ """
+ norm = np.linalg.norm(position)
+ if norm > 1e-10:
+ return position * (norm / np.max(np.abs(position)))
+ else:
+ return position
+
+
+def coord_trans(position, cartesian):
+ """Transform from ADM position information to a unit Cartesian direction
+ vector and a distance.
+
+ Parameters:
+ position (PolarPosition or CartesianPosition): If Cartesian, has keys X, Y, Z; otherwise has keys
+ azimuth, elevation, distance.
+ cartesian (bool): Block format 'cartesian' flag.
+
+ Returns:
+ array of shape (3,): The direction component as a normalised Cartesian vector.
+ float: The distance component of the source.
+ """
+ if isinstance(position, ObjectPolarPosition):
+ cart_pos = cart(position.azimuth, position.elevation, position.distance)
+ elif isinstance(position, ObjectCartesianPosition):
+ cart_pos = position.as_cartesian_array()
+ else:
+ assert False, "position should be ObjectPolarPosition or ObjectCartesianPosition" # pragma: no cover
+
+ if cartesian:
+ cart_pos = cube_to_sphere(cart_pos)
+
+ return cart_pos
+
+
+class ChannelLockHandler(object):
+ """Implementation of channel locking as a position transformation."""
+
+ def __init__(self, layout):
+ self.positions = layout.norm_positions
+
+ azimuths = np.array([channel.polar_position.azimuth for channel in layout.channels])
+ elevations = np.array([channel.polar_position.elevation for channel in layout.channels])
+
+ # define a priority for channels, used to select a single channel when
+ # multiple channels are the same distance from the position. Channels
+ # with the lowest absolute elevation have the highest priority, with
+ # ties broken by elevation, absolute azimuth then azimuth.
+ priority_order = np.lexsort((azimuths, np.abs(azimuths),
+ elevations, np.abs(elevations)))
+ self.channel_priority = np.arange(len(layout.channels))[priority_order]
+
+ def handle(self, position, channelLock):
+ """Apply channel lock to a position.
+
+ Parameters:
+ position (array of length 3): Cartesian source position
+ channelLock (fileio.adm.elements.ChannelLock or None):
+ Channel lock information if it is enabled
+ Returns:
+ array of shape (3,) representing a Cartesian position.
+ """
+ tol = 1e-5
+
+ if channelLock is not None:
+ distances = np.linalg.norm(position - self.positions, axis=1)
+ min_dist = np.min(distances)
+
+ all_closest = np.where(distances < min_dist + tol)[0]
+ all_closest_priorities = self.channel_priority[all_closest]
+
+ closest = all_closest[np.argmin(all_closest_priorities)]
+
+ if channelLock.maxDistance is None or distances[closest] <= channelLock.maxDistance + tol:
+ return self.positions[closest]
+ return position
+
+
+def diverge(position, objectDivergence, cartesian):
+ """Implement object divergence by duplicating and modifying source
+ directions.
+
+ Parameters:
+ position (array of length 3): Cartesian source position
+ objectDivergence (fileio.adm.elements.ObjectDivergence): object divergence information
+ cartesian (bool): Block format 'cartesian' flag.
+
+ Returns:
+ array of length n: gain for each position
+ array of shape (n, 3): modified source positions
+ """
+ if objectDivergence is None or objectDivergence.value == 0.0:
+ return np.array([1.0]), position[np.newaxis]
+ else:
+ # Find gains g_l, g_c, g_r for the left, centre and right objects for
+ # divergence value x such that:
+ # - g_l + g_r + g_c = 1 for all x
+ # - g_l = g_r = 0 and g_c = 1 for x = 0
+ # - g_l = g_r = g_c = 1/3 for x = 0.5
+ # - g_l = g_r = 0.5 and g_c = 0 for x = 1
+ value = objectDivergence.value
+ g_l = g_r = value / (value + 1)
+ g_c = (1 - value) / (value + 1)
+
+ if cartesian:
+ if objectDivergence.azimuthRange is not None:
+ warnings.warn("azimuthRange specified for blockFormat in Cartesian mode; using Cartesian divergence")
+
+ # apply divergence in cube-space then translate back
+ pos_cube = sphere_to_cube(position)
+
+ positionRange = (objectDivergence.positionRange
+ if objectDivergence.positionRange is not None
+ else 0.0)
+
+ pos_left_cube = pos_cube + np.array([positionRange, 0, 0])
+ pos_right_cube = pos_cube - np.array([positionRange, 0, 0])
+
+ pos_centre = cube_to_sphere(pos_cube)
+ pos_left = cube_to_sphere(pos_left_cube)
+ pos_right = cube_to_sphere(pos_right_cube)
+
+ return np.array([g_l, g_c, g_r]), np.array([pos_left, pos_centre, pos_right])
+ else:
+ if objectDivergence.positionRange is not None:
+ warnings.warn("positionRange specified for blockFormat in polar mode; using polar divergence")
+
+ azimuthRange = (objectDivergence.azimuthRange
+ if objectDivergence.azimuthRange is not None
+ else 45.0)
+
+ distance = np.linalg.norm(position)
+ p_l, p_r = cart(azimuthRange, 0, distance), cart(-azimuthRange, 0, distance)
+
+ M = local_coordinate_system(azimuth(position), elevation(position)).T
+ p_l = np.dot(M, p_l)
+ p_r = np.dot(M, p_r)
+
+ return np.array([g_l, g_c, g_r]), np.array([p_l, position, p_r])
+
+
+class ExtentHandler(object):
+ """Implementation of extent panning that also handles point source panning
+ for zero-size objects.
+ """
+
+ def __init__(self, point_source_panner):
+ """
+ Parameters:
+ point_source_panner (point_source.PointSourcePanner): point source
+ panner to use
+ """
+ self.polar_extent_panner = extent.PolarExtentPanner(point_source_panner.handle)
+ self.cart_extent_panner = extent.CartExtentPanner(point_source_panner.handle,
+ spreading_panner=self.polar_extent_panner.spreading_panner)
+
+ @classmethod
+ def extent_mod(cls, extent, distance):
+ """Modify an extent parameter given a distance.
+
+ A right triangle if formed, with the adjacent edge being the distance,
+ and the opposite edge being determined from the extent. The angle
+ formed is then used to determine the new extent.
+
+ - at distance=0, the extent is always 360
+ - at distance=1, the original extent is used
+ - at distance>1, the extent decreases
+ - in 0 < distance < 1, the extent changes more steeply around 0 for smaller extents
+ """
+ min_size = 0.2
+ size = np.interp(extent, [0, 360], [min_size, 1.0])
+ extent_1 = 4 * np.degrees(np.arctan2(size, 1.0))
+ return np.interp(4 * np.degrees(np.arctan2(size, distance)),
+ [0, extent_1, 360.0],
+ [0, extent, 360.0])
+
+ def handle(self, position, width, height, depth, cartesian):
+ """Calculate loudspeaker gains given position and extent parameters.
+
+ Parameters:
+ position (array of length 3): Cartesian source position
+ width (float): block format width parameter
+ height (float): block format height parameter
+ depth (float): block format depth parameter
+ distance (float): distance part of the position
+ cartesian (bool): is this block format in Cartesian mode?
+ Returns:
+ gain (array of length n): loudspeaker gains of length
+ self.point_source_panner.num_channels.
+ """
+ if cartesian:
+ size = np.array([width, depth, height])
+ return self.cart_extent_panner.calc_pv_spread(position, size)
+ else:
+ distance = np.linalg.norm(position)
+
+ if depth != 0:
+ distances = np.array([distance + depth / 2.0,
+ distance - depth / 2.0])
+ distances[distances < 0] = 0.0
+ else:
+ distances = [distance]
+
+ pvs = [self.polar_extent_panner.calc_pv_spread(position,
+ self.extent_mod(width, end_distance),
+ self.extent_mod(height, end_distance))
+ for end_distance in distances]
+
+ if len(pvs) == 1:
+ return pvs[0]
+ else:
+ return np.sqrt(np.mean(np.square(pvs), axis=0))
+
+
+class ZoneExclusionHandler(object):
+
+ def __init__(self, layout):
+ self.num_channels = len(layout.channels)
+
+ self.positions = layout.nominal_positions
+ self.azimuths = np.array([channel.polar_nominal_position.azimuth for channel in layout.channels])
+ self.elevations = np.array([channel.polar_nominal_position.elevation for channel in layout.channels])
+
+ self.zed = ZoneExclusionDownmix(layout)
+
+ def get_excluded(self, zoneExclusion):
+ excluded = np.zeros(self.num_channels, dtype=bool)
+
+ epsilon = 1e-6
+
+ for zone in zoneExclusion:
+ if isinstance(zone, CartesianZone):
+ excluded |= (
+ (self.positions[:, 0] - epsilon < zone.maxX) &
+ (self.positions[:, 1] - epsilon < zone.maxY) &
+ (self.positions[:, 2] - epsilon < zone.maxZ) &
+ (self.positions[:, 0] + epsilon > zone.minX) &
+ (self.positions[:, 1] + epsilon > zone.minY) &
+ (self.positions[:, 2] + epsilon > zone.minZ)
+ )
+ elif isinstance(zone, PolarZone):
+ excluded |= (
+ (self.elevations - epsilon < zone.maxElevation) &
+ (self.elevations + epsilon > zone.minElevation) &
+ (
+ # speakers at the poles have indeterminate elevation and should match any range
+ (np.abs(self.elevations) > 90.0 - epsilon) |
+ [inside_angle_range(az, zone.minAzimuth, zone.maxAzimuth, tol=epsilon)
+ for az in self.azimuths]
+ )
+ )
+ else:
+ assert False, "wrong type in zone" # pragma: no cover
+
+ return excluded
+
+ def handle(self, gains, zoneExclusion):
+ excluded = self.get_excluded(zoneExclusion)
+ downmix = self.zed.downmix_for_excluded(excluded)
+ return np.sqrt(np.dot(gains**2, downmix))
+
+
+DirectDiffuseGains = namedtuple("DirectDiffuseGains", ["direct", "diffuse"])
+
+
+def direct_diffuse_split(gains, diffuse):
+ """Split gains into a direct and diffuse path.
+
+ Parameters:
+ gains (array of n floats): input gains
+ diffuse (float): ADM diffuse parameter
+
+ Returns:
+ DirectDiffuseGains: gains for direct and diffuse paths
+ """
+ return DirectDiffuseGains(
+ direct=gains * np.sqrt(1.0 - diffuse),
+ diffuse=gains * np.sqrt(diffuse)
+ )
+
+
+class GainCalc(object):
+ options = OptionsHandler(
+ point_source_opts=SubOptions(
+ handler=point_source.configure_options,
+ description="options for point source panner",
+ ),
+ )
+
+ @options.with_defaults
+ def __init__(self, layout, point_source_opts):
+ self.point_source_panner = point_source.configure(layout.without_lfe, **point_source_opts)
+ self.screen_edge_lock_handler = ScreenEdgeLockHandler(layout.screen)
+ self.screen_scale_handler = ScreenScaleHandler(layout.screen)
+ self.channel_lock_handler = ChannelLockHandler(layout.without_lfe)
+ self.extent_panner = ExtentHandler(self.point_source_panner)
+ self.zone_exclusion_handler = ZoneExclusionHandler(layout.without_lfe)
+
+ self.is_lfe = layout.is_lfe
+ self.lfe_downmix_matrix = lfe_downmix_matrix(layout)
+
+ def render(self, object_meta):
+ block_format = object_meta.block_format
+
+ position = coord_trans(block_format.position, block_format.cartesian)
+
+ position = self.screen_scale_handler.handle(position, block_format.screenRef, object_meta.extra_data.reference_screen)
+
+ position = self.screen_edge_lock_handler.handle_vector(position, block_format.position.screenEdgeLock)
+
+ position = self.channel_lock_handler.handle(position, block_format.channelLock)
+
+ diverged_gains, diverged_positions = diverge(position, block_format.objectDivergence, block_format.cartesian)
+
+ gains_for_each_pos = np.apply_along_axis(self.extent_panner.handle, 1, diverged_positions,
+ block_format.width, block_format.height, block_format.depth,
+ block_format.cartesian)
+
+ gains = np.sqrt(np.dot(diverged_gains, gains_for_each_pos**2))
+
+ gains = self.zone_exclusion_handler.handle(gains, block_format.zoneExclusion)
+
+ gains = np.nan_to_num(gains)
+
+ if is_lfe(object_meta.extra_data.channel_frequency):
+ gains_full = np.dot(self.lfe_downmix_matrix, gains)
+ gains_full /= np.sum(gains_full)
+
+ gains_full *= block_format.gain
+
+ return DirectDiffuseGains(
+ direct=gains_full,
+ diffuse=np.zeros_like(gains_full)
+ )
+ else:
+ gains *= block_format.gain
+
+ gains_full = np.zeros(len(self.is_lfe))
+ gains_full[~self.is_lfe] = gains
+
+ return direct_diffuse_split(gains_full, block_format.diffuse)
diff --git a/ear/core/objectbased/renderer.py b/ear/core/objectbased/renderer.py
new file mode 100644
index 00000000..fea72e32
--- /dev/null
+++ b/ear/core/objectbased/renderer.py
@@ -0,0 +1,157 @@
+import numpy as np
+import math
+from fractions import Fraction
+from ..convolver import OverlapSaveConvolver, VariableBlockSizeAdapter
+from ..delay import Delay
+from ...options import Option, SubOptions, OptionsHandler
+from .gain_calc import GainCalc
+from . import decorrelate
+from ..renderer_common import BlockProcessingChannel, InterpretTimingMetadata, InterpGains, FixedGains
+
+
+class InterpretObjectMetadata(InterpretTimingMetadata):
+ """Interpret a sequence of ObjectTypeMetadata, producing a sequence of ProcessingBlock.
+
+ Args:
+ calc_gains (callable): Called with ObjectTypeMetadata to calculate per-channel gains.
+ """
+
+ def __init__(self, calc_gains):
+ super(InterpretObjectMetadata, self).__init__()
+ self.calc_gains = calc_gains
+
+ self.last_block_end = None
+ self.last_block_gains = None
+
+ @classmethod
+ def interp_length(cls, block_format, duration):
+ if block_format.jumpPosition.flag:
+ if block_format.jumpPosition.interpolationLength is not None:
+ return block_format.jumpPosition.interpolationLength
+ else:
+ return Fraction(0)
+ else:
+ return duration
+
+ def __call__(self, sample_rate, block):
+ """Yield ProcessingBlock that apply the processing for a given ObjectTypeMetadata.
+
+ Args:
+ sample_rate (int): Sample rate to operate in.
+ block (ObjectTypeMetadata): Metadata to interpret.
+
+ Yields:
+ One or two ProcessingBlock objects that apply gains for a single input channel.
+ """
+ start_time, end_time = self.block_start_end(block)
+ interp_time = self.interp_length(block.block_format, end_time - start_time)
+ target_time = start_time + interp_time
+
+ if target_time > end_time:
+ raise Exception("specified interpolation length is longer than block {0.id}".format(block.block_format))
+
+ # if this block starts immediately after a previous block, interpolate
+ # from it, otherwise, don't do any interpolation.
+ if self.last_block_end is not None and start_time == self.last_block_end:
+ interp_from = self.last_block_gains
+ else:
+ target_time = start_time
+ interp_from = None
+
+ interp_to = self.calc_gains(block)
+
+ start_sample = start_time * sample_rate
+ end_sample = end_time * sample_rate
+ target_sample = target_time * sample_rate
+
+ if start_sample != target_sample:
+ assert not math.isinf(target_sample)
+ yield InterpGains(start_sample, target_sample, interp_from, interp_to)
+ if target_sample != end_sample:
+ assert not math.isinf(target_sample)
+ yield FixedGains(target_sample, end_sample, interp_to)
+
+ self.last_block_end = end_time
+ self.last_block_gains = interp_to
+
+
+class ObjectRenderer(object):
+
+ options = OptionsHandler(
+ block_size=Option(
+ default=512,
+ description="block size for decorrelator convolution",
+ ),
+ gain_calc_opts=SubOptions(
+ handler=GainCalc.options,
+ description="options for gain calculator",
+ ),
+ decorrelator_opts=SubOptions(
+ handler=decorrelate.design_options,
+ description="options for decorrelation filter design",
+ ),
+ )
+
+ @options.with_defaults
+ def __init__(self, layout, gain_calc_opts, decorrelator_opts, block_size):
+ self._gain_calc = GainCalc(layout, **gain_calc_opts)
+ self._nchannels = len(layout.channels)
+
+ # tuples of an input channel number and a BlockProcessingChannel to apply to that
+ # input channel.
+ self.block_processing_channels = []
+
+ decorrlation_filters = decorrelate.design_decorrelators(layout, **decorrelator_opts)
+ decorrelator_delay = (decorrlation_filters.shape[0] - 1) // 2
+
+ decorrelators = OverlapSaveConvolver(
+ block_size, self._nchannels, decorrlation_filters)
+ self.decorrelators_vbs = VariableBlockSizeAdapter(
+ block_size, self._nchannels, decorrelators.filter_block)
+
+ self.overall_delay = self.decorrelators_vbs.delay(decorrelator_delay)
+
+ self.delays = Delay(self._nchannels, self.overall_delay)
+
+ def _calc_gains(self, block):
+ gains = self._gain_calc.render(block)
+ return np.concatenate((gains.direct, gains.diffuse))
+
+ def set_rendering_items(self, rendering_items):
+ """Set the rendering items to process.
+
+ Note:
+ Since this resets the internal state, this should normally be called
+ once before rendering is started. Dynamic modification of the
+ rendering items could be implemented though another API.
+
+ Args:
+ rendering_items (list of ObjectRenderingItem): Items to process.
+ """
+ self.block_processing_channels = [(item.track_index,
+ BlockProcessingChannel(item.metadata_source,
+ InterpretObjectMetadata(self._calc_gains)))
+ for item in rendering_items]
+
+ def render(self, sample_rate, start_sample, input_samples):
+ """Process n input samples to produce n output samples.
+
+ Args:
+ sample_rate (int): Sample rate.
+ start_sample (int): Index of the first sample in input_samples.
+ input_samples (ndarray of (k, k) float): Multi-channel input sample
+ block; there must be at least as many channels as referenced in the
+ rendering items.
+
+ Returns:
+ (ndarray of (n, l) float): l channels of output samples
+ corresponding to the l loudspeakers in layout.
+ """
+ interpolated = np.zeros((len(input_samples), self._nchannels * 2))
+
+ for channel_no, block_processing in self.block_processing_channels:
+ block_processing.process(sample_rate, start_sample, input_samples[:, channel_no], interpolated)
+
+ direct_out = self.delays.process(interpolated[:, :self._nchannels])
+ diffuse_out = self.decorrelators_vbs.process(interpolated[:, self._nchannels:])
+ return direct_out + diffuse_out
diff --git a/ear/core/objectbased/test/__init__.py b/ear/core/objectbased/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/core/objectbased/test/conftest.py b/ear/core/objectbased/test/conftest.py
new file mode 100644
index 00000000..95487568
--- /dev/null
+++ b/ear/core/objectbased/test/conftest.py
@@ -0,0 +1,29 @@
+import pytest
+from ... import bs2051
+from ..gain_calc import GainCalc
+
+
+@pytest.fixture(scope="module")
+def layout():
+ return bs2051.get_layout("4+5+0")
+
+
+@pytest.fixture(scope="module")
+def layout_without_lfe(layout_with_lfe):
+ return layout_with_lfe.without_lfe
+
+
+@pytest.fixture(scope="module")
+def gain_calc(layout):
+ return GainCalc(layout)
+
+
+@pytest.fixture(params=bs2051.layout_names)
+def any_layout_with_lfe(request):
+ """Layout object."""
+ return bs2051.get_layout(request.param)
+
+
+@pytest.fixture
+def any_layout(any_layout_with_lfe):
+ return any_layout_with_lfe.without_lfe
diff --git a/ear/core/objectbased/test/data/gain_calc_pvs/inputs.pickle b/ear/core/objectbased/test/data/gain_calc_pvs/inputs.pickle
new file mode 100644
index 00000000..e5a3b90b
Binary files /dev/null and b/ear/core/objectbased/test/data/gain_calc_pvs/inputs.pickle differ
diff --git a/ear/core/objectbased/test/data/gain_calc_pvs/outputs.npz b/ear/core/objectbased/test/data/gain_calc_pvs/outputs.npz
new file mode 100644
index 00000000..376ff7b5
Binary files /dev/null and b/ear/core/objectbased/test/data/gain_calc_pvs/outputs.npz differ
diff --git a/ear/core/objectbased/test/test_decorrelate.py b/ear/core/objectbased/test/test_decorrelate.py
new file mode 100644
index 00000000..649f847b
--- /dev/null
+++ b/ear/core/objectbased/test/test_decorrelate.py
@@ -0,0 +1,49 @@
+import numpy as np
+import numpy.testing as npt
+import pytest
+from ..decorrelate import gen_rand_mt19937, design_decorrelator_basic, design_decorrelators
+from ... import bs2051
+
+
+def test_gen_rand_mt19937():
+ # test data from C++11 standard
+ seed = 5489
+ i = 10000
+ expected = 4123659995
+ assert gen_rand_mt19937(seed, i)[i-1] == expected
+
+
+def test_design_decorrelator():
+ rand = gen_rand_mt19937(0, 1)[0] / float(2**32)
+ expected = [1.0, np.exp(2j * rand * np.pi)]
+
+ filt = design_decorrelator_basic(0)
+ assert len(filt) == 512
+ assert filt.dtype == float
+ npt.assert_allclose(np.fft.fft(filt)[:2], expected)
+
+
+@pytest.mark.parametrize("method_name, method_f", [
+ ("basic", design_decorrelator_basic),
+])
+def test_design_decorrelators(method_name, method_f):
+ layout = bs2051.get_layout("4+5+0").without_lfe
+ filters = design_decorrelators(layout, method=method_name)
+
+ # M+030 should get the second filter
+ right_filter = filters[:, layout.channel_names.index("M+030")]
+ npt.assert_allclose(right_filter, method_f(1))
+
+
+def correlation_coefficient(a, b):
+ cross_corr = np.fft.irfft(np.fft.rfft(a) * np.fft.rfft(b[::-1]))
+ return cross_corr[np.argmax(np.abs(cross_corr))]
+
+
+def correlation_coefficient_matrix(filters):
+ @np.vectorize
+ def corr_idx(a, b):
+ return correlation_coefficient(filters[:, a], filters[:, b])
+
+ A, B = np.ogrid[:filters.shape[1], :filters.shape[1]]
+ return corr_idx(A, B)
diff --git a/ear/core/objectbased/test/test_extent.py b/ear/core/objectbased/test/test_extent.py
new file mode 100644
index 00000000..2400b0a6
--- /dev/null
+++ b/ear/core/objectbased/test/test_extent.py
@@ -0,0 +1,95 @@
+from ..extent import calc_basis, azimuth_elevation_on_basis, cart_on_basis, PolarExtentPanner
+from ...geom import cart
+from ... import bs2051, point_source
+import numpy as np
+import numpy.testing as npt
+
+
+def test_basis():
+ # cardinal directions
+ npt.assert_allclose(calc_basis(cart(0, 0, 1)),
+ np.eye(3), atol=1e-10)
+ npt.assert_allclose(calc_basis(cart(90, 0, 1)),
+ [[0, 1, 0],
+ [-1, 0, 0],
+ [0, 0, 1]], atol=1e-10)
+ npt.assert_allclose(calc_basis(cart(-90, 0, 1)),
+ [[0, -1, 0],
+ [1, 0, 0],
+ [0, 0, 1]], atol=1e-10)
+ npt.assert_allclose(calc_basis(cart(180, 0, 1)),
+ [[-1, 0, 0],
+ [0, -1, 0],
+ [0, 0, 1]], atol=1e-10)
+ npt.assert_allclose(calc_basis(cart(0, 90, 1)),
+ [[1, 0, 0],
+ [0, 0, 1],
+ [0, -1, 0]], atol=1e-10)
+ npt.assert_allclose(calc_basis(cart(0, -90, 1)),
+ [[1, 0, 0],
+ [0, 0, -1],
+ [0, 1, 0]], atol=1e-10)
+
+ # slight offset from pole should behave as if pointing forwards
+ npt.assert_allclose(calc_basis(cart(90, 90-1e-6, 1)),
+ [[1, 0, 0],
+ [0, 0, 1],
+ [0, -1, 0]], atol=1e-7)
+ npt.assert_allclose(calc_basis(cart(90, -90+1e-6, 1)),
+ [[1, 0, 0],
+ [0, 0, -1],
+ [0, 1, 0]], atol=1e-7)
+
+
+def test_az_el_on_basis():
+ basis = calc_basis(cart(0, 10, 1))
+ npt.assert_allclose(azimuth_elevation_on_basis(basis, cart(0, 10, 1)), (0, 0), atol=1e-10)
+ npt.assert_allclose(azimuth_elevation_on_basis(basis, cart(0, 20, 1)), (0, np.radians(10)), atol=1e-10)
+ basis = calc_basis(cart(-10, 0, 1))
+ npt.assert_allclose(azimuth_elevation_on_basis(basis, cart(-20, 0, 1)), (np.radians(10), 0), atol=1e-10)
+
+
+def test_cart_on_basis():
+ basis = calc_basis(cart(0, 10, 1))
+ npt.assert_allclose(cart_on_basis(basis, 0, np.radians(10)), cart(0, 20, 1), atol=1e-10)
+ basis = calc_basis(cart(-10, 0, 1))
+ npt.assert_allclose(cart_on_basis(basis, np.radians(10), 0), cart(-20, 0, 1), atol=1e-10)
+
+
+def test_weight_func():
+ fade = PolarExtentPanner.fade_width
+ height = 10
+
+ for swap in [lambda a, b, *args: (a, b) + args, lambda a, b, *args: (b, a) + args]:
+ for width, azimuth in [(20, 0), (360, 0), (360, 180)]:
+ elevations = np.linspace(-90, 90)
+ points = cart(*swap(azimuth, elevations, 1))
+ expected = np.interp(elevations,
+ [-(height/2+fade), -height/2, height/2, height/2+fade],
+ [0, 1, 1, 0])
+ actual = PolarExtentPanner.get_weight_func(cart(0, 0, 1), *swap(width, height))(points)
+ npt.assert_allclose(actual, expected)
+
+ azimuths = np.linspace(-180, 180)
+ points = cart(*swap(azimuths, 0, 1))
+ expected = np.interp(azimuths,
+ [-(width/2+fade), -width/2, width/2, width/2+fade],
+ [0, 1, 1, 0])
+ actual = PolarExtentPanner.get_weight_func(cart(0, 0, 1), *swap(width, height))(points)
+ npt.assert_allclose(actual, expected)
+
+
+def test_pv():
+ layout = bs2051.get_layout("9+10+3").without_lfe
+ psp = point_source.configure(layout)
+ ep = PolarExtentPanner(psp.handle)
+
+ npt.assert_allclose(ep.calc_pv_spread(cart(0, 0, 1), 0, 0), psp.handle(cart(0, 0, 1)))
+ npt.assert_allclose(ep.calc_pv_spread(cart(10, 20, 1), 0, 0), psp.handle(cart(10, 20, 1)))
+
+ for pos, tol in [(cart(0, 0, 1), 1e-10), (cart(30, 10, 1), 1e-2)]:
+ spread_pv = ep.calc_pv_spread(pos, 20, 10)
+ npt.assert_allclose(np.linalg.norm(spread_pv), 1)
+ vv = np.dot(spread_pv, layout.positions)
+ vv /= np.linalg.norm(vv)
+ npt.assert_allclose(vv, pos, atol=tol)
diff --git a/ear/core/objectbased/test/test_gain_calc.py b/ear/core/objectbased/test/test_gain_calc.py
new file mode 100644
index 00000000..f91f2ab1
--- /dev/null
+++ b/ear/core/objectbased/test/test_gain_calc.py
@@ -0,0 +1,408 @@
+from attr import attrs, attrib, Factory, evolve
+import pytest
+import numpy as np
+import numpy.testing as npt
+from ..gain_calc import GainCalc
+from ....fileio.adm.elements import (AudioBlockFormatObjects, ChannelLock, ObjectDivergence,
+ CartesianZone, PolarZone, Frequency, ScreenEdgeLock, ObjectPolarPosition)
+from ...metadata_input import ObjectTypeMetadata, ExtraData
+from ...geom import cart, elevation, PolarPosition
+from ....common import PolarScreen
+from ...test.test_screen_common import default_edge_elevation
+
+
+@attrs
+class GainCalcTestCase(object):
+ name = attrib()
+ block_format = attrib()
+ extra_data = attrib(default=Factory(ExtraData))
+
+ direct_gains = attrib(default=Factory(list))
+ diffuse_gains = attrib(default=Factory(list))
+
+ direct_position = attrib(default=None)
+ diffuse_position = attrib(default=None)
+
+ def _get_gains(self, layout, gain_calc, position, gains):
+ if position is not None:
+ gains = gain_calc.point_source_panner.handle(position)
+ gains_full = np.zeros(len(layout.channels))
+ gains_full[~layout.is_lfe] = gains
+ return gains_full
+
+ else:
+ expected_direct = np.zeros(len(layout.channels))
+ for name, gain in gains:
+ expected_direct[layout.channel_names.index(name)] = gain
+ return expected_direct
+
+ def get_direct_gains(self, layout, gain_calc):
+ return self._get_gains(layout, gain_calc, self.direct_position, self.direct_gains)
+
+ def get_diffuse_gains(self, layout, gain_calc):
+ return self._get_gains(layout, gain_calc, self.diffuse_position, self.diffuse_gains)
+
+ def run(self, layout, gain_calc):
+ block_format = AudioBlockFormatObjects(**self.block_format)
+ gains = gain_calc.render(ObjectTypeMetadata(block_format=block_format,
+ extra_data=self.extra_data))
+
+ expected_direct = self.get_direct_gains(layout, gain_calc)
+ expected_diffuse = self.get_diffuse_gains(layout, gain_calc)
+
+ npt.assert_allclose(gains.diffuse, expected_diffuse, atol=1e-10)
+ npt.assert_allclose(gains.direct, expected_direct, atol=1e-10)
+
+
+test_cases = []
+
+
+def add_test(*args, **kwargs):
+ test_cases.append(GainCalcTestCase(*args, **kwargs))
+
+
+add_test("basic_centre",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M+000", 1.0)])
+add_test("basic_left",
+ dict(position=dict(azimuth=30.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M+030", 1.0)])
+add_test("basic_left_up",
+ dict(position=dict(azimuth=30.0, elevation=30.0, distance=1.0)),
+ direct_gains=[("U+030", 1.0)])
+
+add_test("diffuse_half",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0), diffuse=0.5),
+ direct_gains=[("M+000", np.sqrt(0.5))],
+ diffuse_gains=[("M+000", np.sqrt(0.5))])
+add_test("diffuse_full",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0), diffuse=1.0),
+ diffuse_gains=[("M+000", 1.0)])
+
+add_test("gain",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0), gain=0.5),
+ direct_gains=[("M+000", 0.5)])
+
+add_test("coord_trans_cart_cart",
+ dict(position=dict(zip("XYZ", cart(30, 30, 4.0/3.0))), cartesian=True),
+ direct_gains=[("U+030", 1.0)])
+add_test("coord_trans_polar_cart",
+ dict(position=dict(azimuth=30.0, elevation=30.0, distance=4.0/3.0), cartesian=True),
+ direct_gains=[("U+030", 1.0)])
+add_test("coord_trans_cart_polar",
+ dict(position=dict(zip("XYZ", cart(30, 30, 1.0)))),
+ direct_gains=[("U+030", 1.0)])
+add_test("coord_trans_polar_polar",
+ dict(position=dict(azimuth=30.0, elevation=30.0, distance=1.0)),
+ direct_gains=[("U+030", 1.0)])
+
+add_test("channel_lock_on_speaker",
+ dict(channelLock=ChannelLock(maxDistance=1.0),
+ position=dict(azimuth=0.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M+000", 1.0)])
+add_test("channel_lock_close",
+ dict(channelLock=ChannelLock(maxDistance=1.0),
+ position=dict(azimuth=14.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M+000", 1.0)])
+add_test("channel_lock_not_close_enough",
+ dict(channelLock=ChannelLock(maxDistance=np.linalg.norm(cart(0, 0, 1) - cart(15, 0, 1)) - 0.01),
+ position=dict(azimuth=15.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M+000", np.sqrt(0.5)), ("M+030", np.sqrt(0.5))])
+
+add_test("channel_lock_abs_elevation_priority",
+ dict(channelLock=ChannelLock(),
+ position=dict(azimuth=30.0, elevation=15.0, distance=1.0)),
+ direct_gains=[("M+030", 1.0)])
+add_test("channel_lock_abs_az_priority_left",
+ dict(channelLock=ChannelLock(),
+ position=dict(azimuth=15.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M+000", 1.0)])
+add_test("channel_lock_abs_az_priority_right",
+ dict(channelLock=ChannelLock(),
+ position=dict(azimuth=-15.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M+000", 1.0)])
+add_test("channel_lock_az_priority_front_top",
+ dict(channelLock=ChannelLock(),
+ position=dict(azimuth=0.0, elevation=30.0, distance=1.0)),
+ direct_gains=[("U-030", 1.0)])
+add_test("channel_lock_az_priority_rear_top",
+ dict(channelLock=ChannelLock(),
+ position=dict(azimuth=180.0, elevation=30.0, distance=1.0)),
+ direct_gains=[("U-110", 1.0)])
+add_test("channel_lock_az_priority_rear_mid",
+ dict(channelLock=ChannelLock(),
+ position=dict(azimuth=180.0, elevation=0.0, distance=1.0)),
+ direct_gains=[("M-110", 1.0)])
+
+add_test("diverge_half",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0),
+ objectDivergence=ObjectDivergence(0.5, azimuthRange=30.0)),
+ direct_gains=[("M+000", np.sqrt(1.0/3.0)), ("M+030", np.sqrt(1.0/3.0)), ("M-030", np.sqrt(1.0/3.0))])
+add_test("diverge_full",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0),
+ objectDivergence=ObjectDivergence(1.0, azimuthRange=30.0)),
+ direct_gains=[("M+030", np.sqrt(0.5)), ("M-030", np.sqrt(0.5))])
+
+add_test("diverge_cart",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0), cartesian=True,
+ objectDivergence=ObjectDivergence(0.5, positionRange=np.tan(np.radians(30.0)))),
+ direct_gains=[("M+000", np.sqrt(1.0/3.0)), ("M+030", np.sqrt(1.0/3.0)), ("M-030", np.sqrt(1.0/3.0))])
+
+add_test("diverge_azimuth",
+ dict(position=dict(azimuth=(30.0+110.0)/2.0, elevation=0.0, distance=1.0),
+ objectDivergence=ObjectDivergence(1.0, azimuthRange=(110-30.0)/2.0)),
+ direct_gains=[("M+030", np.sqrt(0.5)), ("M+110", np.sqrt(0.5))])
+add_test("diverge_elevation",
+ dict(position=dict(azimuth=0.0, elevation=elevation(cart(30, 30, 1) * [0, 1, 1]), distance=1.0),
+ objectDivergence=ObjectDivergence(1.0, azimuthRange=np.degrees(np.arcsin(cart(-30, 30, 1)[0])))),
+ direct_gains=[("U+030", np.sqrt(0.5)), ("U-030", np.sqrt(0.5))])
+add_test("diverge_azimuth_elevation",
+ dict(position=dict(azimuth=70.0, elevation=elevation(cart(40, 30, 1) * [0, 1, 1]), distance=1.0),
+ objectDivergence=ObjectDivergence(1.0, azimuthRange=np.degrees(np.arcsin(cart(-40, 30, 1)[0])))),
+ direct_gains=[("U+030", np.sqrt(0.5)), ("U+110", np.sqrt(0.5))])
+
+add_test("zone_front",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0),
+ zoneExclusion=[PolarZone(minAzimuth=0.0, maxAzimuth=0.0, minElevation=0.0, maxElevation=0.0)]),
+ direct_gains=[("M+030", np.sqrt(0.5)), ("M-030", np.sqrt(0.5))])
+add_test("zone_mid_front",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0),
+ zoneExclusion=[PolarZone(minAzimuth=-180.0, maxAzimuth=180.0, minElevation=0.0, maxElevation=0.0)]),
+ direct_gains=[("U+030", np.sqrt(0.5)), ("U-030", np.sqrt(0.5))])
+add_test("zone_mid_rear",
+ dict(position=dict(azimuth=180.0, elevation=0.0, distance=1.0),
+ zoneExclusion=[PolarZone(minAzimuth=-180.0, maxAzimuth=180.0, minElevation=0.0, maxElevation=0.0)]),
+ direct_gains=[("U+110", np.sqrt(0.5)), ("U-110", np.sqrt(0.5))])
+
+add_test("lfe_direct",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0)),
+ extra_data=ExtraData(channel_frequency=Frequency(lowPass=120.0)),
+ direct_gains=[("LFE1", 1.0)])
+add_test("lfe_diffuse",
+ dict(position=dict(azimuth=15.0, elevation=0.0, distance=1.0), diffuse=1.0),
+ extra_data=ExtraData(channel_frequency=Frequency(lowPass=120.0)),
+ direct_gains=[("LFE1", 1.0)])
+
+add_test("screen_scale_null",
+ dict(position=dict(azimuth=0.0, elevation=0.0, distance=1.0), screenRef=True),
+ direct_gains=[("M+000", 1.0)])
+
+add_test("screen_scale_right",
+ dict(position=dict(azimuth=30.0, elevation=0.0, distance=1.0), screenRef=True),
+ direct_gains=[("M+000", 1.0)],
+ extra_data=ExtraData(
+ reference_screen=PolarScreen(aspectRatio=1.5,
+ centrePosition=PolarPosition(30.0, 0.0, 1.0),
+ widthAzimuth=30.0)))
+
+add_test("screen_edge_lock_right",
+ dict(position=ObjectPolarPosition(azimuth=0.0, elevation=0.0, distance=1.0,
+ screenEdgeLock=ScreenEdgeLock(horizontal="right"))),
+ direct_position=cart(-29, 0, 1))
+add_test("screen_edge_lock_left",
+ dict(position=ObjectPolarPosition(azimuth=0.0, elevation=0.0, distance=1.0,
+ screenEdgeLock=ScreenEdgeLock(horizontal="left"))),
+ direct_position=cart(29, 0, 1))
+add_test("screen_edge_lock_top",
+ dict(position=ObjectPolarPosition(azimuth=0.0, elevation=0.0, distance=1.0,
+ screenEdgeLock=ScreenEdgeLock(vertical="top"))),
+ direct_position=cart(0, default_edge_elevation, 1))
+add_test("screen_edge_lock_bottom",
+ dict(position=ObjectPolarPosition(azimuth=0.0, elevation=0.0, distance=1.0,
+ screenEdgeLock=ScreenEdgeLock(vertical="bottom"))),
+ direct_position=cart(0, -default_edge_elevation, 1))
+add_test("screen_edge_lock_top_right",
+ dict(position=ObjectPolarPosition(azimuth=0.0, elevation=0.0, distance=1.0,
+ screenEdgeLock=ScreenEdgeLock(vertical="top", horizontal="right"))),
+ direct_position=cart(-29, default_edge_elevation, 1))
+
+
+@pytest.mark.parametrize("test_case", test_cases, ids=[case.name for case in test_cases])
+def test_objectbased(layout, gain_calc, test_case):
+ test_case.run(layout, gain_calc)
+
+
+def test_no_screen_scale(layout):
+ layout = evolve(layout, screen=None)
+ gain_calc = GainCalc(layout)
+
+ GainCalcTestCase(
+ "screen_scale_no_screen",
+ dict(position=dict(azimuth=30.0, elevation=0.0, distance=1.0), screenRef=True),
+ direct_gains=[("M+030", 1.0)],
+ extra_data=ExtraData(
+ reference_screen=PolarScreen(aspectRatio=1.5,
+ centrePosition=PolarPosition(30.0, 0.0, 1.0),
+ widthAzimuth=30.0)),
+ ).run(layout, gain_calc)
+
+ GainCalcTestCase(
+ "screen_edge_lock_right_no_screen",
+ dict(position=ObjectPolarPosition(azimuth=0.0, elevation=0.0, distance=1.0,
+ screenEdgeLock=ScreenEdgeLock(horizontal="right"))),
+ direct_gains=[("M+000", 1.0)],
+ ).run(layout, gain_calc)
+
+
+def test_objectbased_extent(layout, gain_calc):
+ block_formats = [
+ AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=1),
+ width=360, height=360),
+ AudioBlockFormatObjects(position=dict(X=0.0, Y=1.0, Z=0.0),
+ width=360, height=360),
+ AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=0)),
+ AudioBlockFormatObjects(position=dict(X=0.0, Y=0.0, Z=0.0)),
+ ]
+
+ gains_0 = gain_calc.render(ObjectTypeMetadata(block_formats[0]))
+
+ npt.assert_allclose(np.linalg.norm(gains_0), 1.0)
+ assert (np.all(gains_0.direct[~layout.is_lfe] > 0.1) and
+ np.all(gains_0.diffuse == 0))
+
+ for block_format in block_formats[1:]:
+ gains = gain_calc.render(ObjectTypeMetadata(block_format))
+ npt.assert_allclose(gains.direct, gains_0.direct)
+ npt.assert_allclose(gains.diffuse, gains_0.diffuse)
+
+
+def test_distance(layout, gain_calc):
+ """Check that as the distance is decreased there is more spreading, by
+ calculating the energy vector for a range of distances and checking that
+ the length increases with the distance.
+ """
+ def direct_gains(size, distance):
+ block_format = AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=distance),
+ width=size, height=size)
+ return gain_calc.render(ObjectTypeMetadata(block_format)).direct
+
+ for size in [0, 30]:
+ distances = np.linspace(0, 1, 10)
+ pvs = np.array([direct_gains(size, distance) for distance in distances])
+ ev_len = np.linalg.norm(np.square(pvs).dot(layout.norm_positions), axis=1)
+ assert np.all(np.diff(ev_len) > 0)
+
+
+def test_objectbased_depth(layout, gain_calc):
+ """Check that with depth behaves like spreading but with more weight in the
+ direction of the source."""
+ bf_no_depth = AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=1),
+ width=360, height=360, depth=0)
+ bf_with_depth = AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=0.5),
+ width=0, height=0, depth=1)
+ gains_no_depth = gain_calc.render(ObjectTypeMetadata(bf_no_depth)).direct
+ gains_with_depth = gain_calc.render(ObjectTypeMetadata(bf_with_depth)).direct
+
+ npt.assert_allclose(np.linalg.norm(gains_no_depth), 1.0)
+ npt.assert_allclose(np.linalg.norm(gains_with_depth), 1.0)
+
+ front_idx = layout.channel_names.index("M+000")
+ assert np.all(gains_with_depth[~layout.is_lfe]) > 0 and gains_with_depth[front_idx] > gains_no_depth[front_idx]
+
+
+def test_steps(layout, gain_calc):
+ """Check for steps in combinations of extent-related parameters"""
+ from itertools import product
+
+ e = 1e-4
+ params = [
+ # distance
+ [[0.0, 0.0+e], [0.5], [1.0-e, 1.0, 1.0+e], [2.0]],
+ # width
+ [[0.0, 0.0+e], [180.0], [360.0-e, 360.0]],
+ # height
+ [[0.0, 0.0+e], [180.0-e, 180.0, 180.0+e], [360.0-e, 360.0]],
+ # depth
+ [[0.0, 0.0+e], [0.5], [1.0-e, 1.0]],
+ ]
+
+ for params_region in product(*params):
+ similar_gains = []
+ for distance, width, height, depth in product(*params_region):
+ bf = AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=distance),
+ width=width, height=height, depth=depth)
+ similar_gains.append(gain_calc.render(ObjectTypeMetadata(bf)).direct)
+ for gains in similar_gains[1:]:
+ npt.assert_allclose(gains, similar_gains[0], atol=1e-3)
+
+
+def test_divergence_normalised(layout, gain_calc):
+ for value, azimuthRange in [(1.0, 0.0), (1.0, 10.0), (0.5, 10.0), (1.0, 40.0), (0.3, 10.0), (0.7, 10.0)]:
+ block_format = AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=1),
+ objectDivergence=ObjectDivergence(value, azimuthRange=azimuthRange))
+ gains = gain_calc.render(ObjectTypeMetadata(block_format))
+ npt.assert_allclose(np.linalg.norm(gains), 1.0)
+
+
+def test_zone_exclusion():
+ from ... import bs2051
+ from ..gain_calc import ZoneExclusionHandler
+
+ layout = bs2051.get_layout("9+10+3").without_lfe
+ zeh = ZoneExclusionHandler(layout)
+
+ def check(zoneExclusion, *excludes):
+ expected = np.zeros(len(layout.channels), dtype=bool)
+ for exclude in excludes:
+ expected[layout.channel_names.index(exclude)] = True
+
+ assert np.all(zeh.get_excluded(zoneExclusion) == expected)
+
+ # polar tests around U+045
+
+ # no tolerance
+ check([PolarZone(minAzimuth=40.0, maxAzimuth=50.0, minElevation=25.0, maxElevation=35.0)],
+ "U+045")
+ # wide tolerance
+ check([PolarZone(minAzimuth=45.0, maxAzimuth=45.0, minElevation=30.0, maxElevation=30.0)],
+ "U+045")
+ # each edge of zone just past channel
+ check([PolarZone(minAzimuth=45.1, maxAzimuth=50.0, minElevation=25.0, maxElevation=35.0)])
+ check([PolarZone(minAzimuth=40.0, maxAzimuth=44.9, minElevation=25.0, maxElevation=35.0)])
+ check([PolarZone(minAzimuth=40.0, maxAzimuth=50.0, minElevation=30.1, maxElevation=35.0)])
+ check([PolarZone(minAzimuth=40.0, maxAzimuth=50.0, minElevation=25.0, maxElevation=29.9)])
+
+ # polar tests around M+180; min and max azimuth are always specified clockwise
+
+ # no tolerance
+ check([PolarZone(minAzimuth=180.0, maxAzimuth=-180.0, minElevation=0.0, maxElevation=0.0)],
+ "M+180")
+ # wide tolerance
+ check([PolarZone(minAzimuth=175.0, maxAzimuth=-175.0, minElevation=0.0, maxElevation=0.0)],
+ "M+180")
+ # each edge of zone just past channel
+ check([PolarZone(minAzimuth=-179.9, maxAzimuth=-175.0, minElevation=0.0, maxElevation=0.0)])
+ check([PolarZone(minAzimuth=175.0, maxAzimuth=179.9, minElevation=0.0, maxElevation=0.0)])
+
+ # cartesian tests around M+000
+ check([CartesianZone(minX=0.0, maxX=0.0, minY=1.0, maxY=1.0, minZ=0.0, maxZ=0.0)], "M+000")
+ check([CartesianZone(minX=-0.2, maxX=0.2, minY=0.8, maxY=1.2, minZ=-0.2, maxZ=0.2)], "M+000")
+ # cartesian tests around M+090
+ check([CartesianZone(minX=1.0, maxX=1.0, minY=0.0, maxY=0.0, minZ=0.0, maxZ=0.0)], "M-090")
+ check([CartesianZone(minX=0.9, maxX=1.1, minY=-0.1, maxY=0.1, minZ=-0.1, maxZ=0.1)], "M-090")
+ # cartesian tests around T+000
+ check([CartesianZone(minX=0.0, maxX=0.0, minY=0.0, maxY=0.0, minZ=1.0, maxZ=1.0)], "T+000")
+ check([CartesianZone(minX=-0.1, maxX=0.1, minY=-0.1, maxY=0.1, minZ=0.9, maxZ=1.1)], "T+000")
+
+ check([PolarZone(minAzimuth=-180.0, maxAzimuth=180.0, minElevation=0.0, maxElevation=0.0)],
+ 'M+060', 'M-060', 'M+000', 'M+135', 'M-135', 'M+030', 'M-030', 'M+180', 'M+090', 'M-090')
+
+ check([PolarZone(minAzimuth=0.0, maxAzimuth=0.0, minElevation=90.0, maxElevation=90.0)],
+ "T+000")
+ check([PolarZone(minAzimuth=90.0, maxAzimuth=90.0, minElevation=90.0, maxElevation=90.0)],
+ "T+000")
+
+
+def test_cube_to_sphere():
+ from ..gain_calc import cube_to_sphere, sphere_to_cube
+ pos = np.array([1, 1, 0])
+ pos_sph = cube_to_sphere(pos)
+ pos_cube = sphere_to_cube(pos_sph)
+
+ npt.assert_allclose(pos, pos_cube)
+ npt.assert_allclose(cart(-45, 0, 1), pos_sph)
+
+ npt.assert_allclose(np.array([0.0, 0.0, 0.0]),
+ sphere_to_cube(np.array([0.0, 0.0, 0.0])))
+ npt.assert_allclose(np.array([0.0, 0.0, 0.0]),
+ cube_to_sphere(np.array([0.0, 0.0, 0.0])))
diff --git a/ear/core/objectbased/test/test_gain_calc_changes.py b/ear/core/objectbased/test/test_gain_calc_changes.py
new file mode 100644
index 00000000..b12766e7
--- /dev/null
+++ b/ear/core/objectbased/test/test_gain_calc_changes.py
@@ -0,0 +1,138 @@
+import pytest
+import random
+import numpy as np
+import numpy.testing as npt
+from ....fileio.adm.elements import (AudioBlockFormatObjects, ObjectDivergence,
+ ObjectPolarPosition, ObjectCartesianPosition)
+from ...metadata_input import ObjectTypeMetadata, ExtraData
+from ...geom import PolarPosition
+from ....common import PolarScreen
+
+
+def generate_random_ObjectTypeMetadata(cart_pos=None, cartesian=None,
+ azimuth=None, elevation=None, distance=None,
+ X=None, Y=None, Z=None,
+ screen_edge_lock_horizontal=None,
+ screen_edge_lock_vertical=None,
+ width=None, height=None, depth=None,
+ has_divergence=None, divergence_value=None, divergence_range=None,
+ screenRef=None,
+ ):
+ """Generate a random ObjectTypeMetadata, with some fixed attributes if
+ specified.
+ """
+ if cart_pos is None: cart_pos = random.choice([False, True])
+ if cartesian is None: cartesian = random.choice([False, True])
+
+ if cart_pos:
+ if azimuth is None: azimuth = random.uniform(-180, 180)
+ if elevation is None: elevation = random.uniform(-90, 90)
+ if distance is None: distance = random.uniform(0, 1)
+ position = ObjectPolarPosition(azimuth=azimuth, elevation=elevation, distance=distance)
+ else:
+ if X is None: X = random.uniform(-1, 1)
+ if Y is None: Y = random.uniform(-1, 1)
+ if Z is None: Z = random.uniform(-1, 1)
+ position = ObjectCartesianPosition(X=X, Y=Y, Z=Z)
+
+ if screen_edge_lock_horizontal is None:
+ position.screenEdgeLock.vertical = random.choice([None] * 8 + ["left", "right"])
+ if screen_edge_lock_vertical is None:
+ position.screenEdgeLock.horizontal = random.choice([None] * 8 + ["top", "bottom"])
+
+ if screenRef is None: screenRef = bool(random.randrange(2))
+ reference_screen = PolarScreen(aspectRatio=random.uniform(1, 2),
+ centrePosition=PolarPosition(random.uniform(-180, 180),
+ random.uniform(-90, 90),
+ 1.0),
+ widthAzimuth=random.uniform(10, 100))
+
+ if cartesian:
+ width, height, depth = random.uniform(0, 2), random.uniform(0, 2), random.uniform(0, 2)
+ else:
+ width, height, depth = random.uniform(0, 360), random.uniform(0, 360), random.uniform(0, 1)
+
+ if has_divergence is None: has_divergence = random.choice([False, True])
+ if divergence_value is None: divergence_value = random.uniform(0, 1)
+ if divergence_range is None: divergence_range = random.uniform(0, 1) if cartesian else random.uniform(0, 180)
+
+ if cartesian:
+ objectDivergence = ObjectDivergence(value=divergence_value, positionRange=divergence_range)
+ else:
+ objectDivergence = ObjectDivergence(value=divergence_value, azimuthRange=divergence_range)
+
+ block_format = AudioBlockFormatObjects(position=position,
+ width=width, height=height, depth=depth,
+ cartesian=cartesian,
+ objectDivergence=objectDivergence if has_divergence else None)
+ return ObjectTypeMetadata(block_format=block_format, extra_data=ExtraData(reference_screen=reference_screen))
+
+
+def generate_random_ObjectTypeMetadatas():
+ """Generate a list of ObjectTypeMetadata, including random elements and
+ known edge cases.
+ """
+ for i in range(10):
+ yield generate_random_ObjectTypeMetadata(cart_pos=True, X=0.0, Y=0.0, Z=0.0)
+ for i in range(10):
+ yield generate_random_ObjectTypeMetadata(cart_pos=True, X=0.0, Y=0.0, Z=0.0, width=0.0, height=0.0, depth=0.0)
+
+ for i in range(10):
+ pos = np.random.uniform(-1, 1, 3)
+ pos /= np.max(np.abs(pos))
+ X, Y, Z = pos
+ yield generate_random_ObjectTypeMetadata(cart_pos=True, X=X, Y=Y, Z=Z, width=0.0, height=0.0, depth=0.0)
+ yield generate_random_ObjectTypeMetadata(cart_pos=True, X=X, Y=Y, Z=Z)
+
+ for i in range(10):
+ yield generate_random_ObjectTypeMetadata(cart_pos=False, distance=0.0)
+ for i in range(10):
+ yield generate_random_ObjectTypeMetadata(cart_pos=False, distance=1.0)
+ for i in range(10):
+ yield generate_random_ObjectTypeMetadata(cart_pos=False, distance=0.0, width=0.0, height=0.0, depth=0.0)
+
+ yield generate_random_ObjectTypeMetadata(cart_pos=True, X=0.0, Y=0.0, Z=0.0, screenRef=True)
+ yield generate_random_ObjectTypeMetadata(cart_pos=False, distance=0.0, screenRef=True)
+
+ for i in range(1000):
+ yield generate_random_ObjectTypeMetadata()
+
+
+@pytest.mark.no_cover
+def test_changes_random(layout, gain_calc):
+ """Check that the result of the gain calculator with a selection of
+ parameters stays the same.
+ """
+ import py.path
+ files_dir = py.path.local(__file__).dirpath() / "data" / "gain_calc_pvs"
+
+ import pickle
+ inputs_f = files_dir / "inputs.pickle"
+ outputs_f = files_dir / "outputs.npz"
+
+ if inputs_f.check():
+ with open(str(inputs_f), 'rb') as f:
+ inputs = pickle.load(f)
+ else:
+ inputs = list(generate_random_ObjectTypeMetadatas())
+
+ inputs_f.dirpath().ensure_dir()
+ with open(str(inputs_f), 'wb') as f:
+ pickle.dump(inputs, f, protocol=2)
+
+ pvs = [gain_calc.render(input) for input in inputs]
+ direct = np.array([pv.direct for pv in pvs])
+ diffuse = np.array([pv.diffuse for pv in pvs])
+
+ if outputs_f.check():
+ loaded = np.load(str(outputs_f))
+ loaded_directs = loaded["direct"]
+ loaded_diffuses = loaded["diffuse"]
+
+ for input, pv, loaded_direct, loaded_diffuse in zip(inputs, pvs, loaded_directs, loaded_diffuses):
+ npt.assert_allclose(pv.direct, loaded_direct, err_msg=repr(input))
+ npt.assert_allclose(pv.diffuse, loaded_diffuse, err_msg=repr(input))
+ else:
+ outputs_f.dirpath().ensure_dir()
+ np.savez_compressed(str(outputs_f), direct=direct, diffuse=diffuse)
+ pytest.skip("generated pv file for gain calc")
diff --git a/ear/core/objectbased/test/test_renderer.py b/ear/core/objectbased/test/test_renderer.py
new file mode 100644
index 00000000..bb6e9345
--- /dev/null
+++ b/ear/core/objectbased/test/test_renderer.py
@@ -0,0 +1,45 @@
+from fractions import Fraction
+import numpy as np
+from ..renderer import InterpretObjectMetadata, FixedGains, InterpGains
+from ...metadata_input import ObjectTypeMetadata
+from ....fileio.adm.elements import AudioBlockFormatObjects, JumpPosition
+
+
+def test_interpret_object_metadata():
+ def calc_gains(otm):
+ return np.array([otm.block_format.gain])
+
+ sr = 48000
+ iom = InterpretObjectMetadata(calc_gains)
+
+ def bf_to_states(**kwargs):
+ block_format = AudioBlockFormatObjects(position=dict(azimuth=0, elevation=0, distance=1),
+ **kwargs)
+ return list(iom(sr, ObjectTypeMetadata(block_format=block_format)))
+
+ # fixed for duration of first block
+ assert (bf_to_states(rtime=Fraction(0), duration=Fraction(1), gain=0.1) ==
+ [FixedGains(start_sample=0, end_sample=sr, gains=[0.1])])
+
+ # interpolate over next regular adjacent block
+ assert (bf_to_states(rtime=Fraction(1), duration=Fraction(1), gain=0.2) ==
+ [InterpGains(start_sample=sr, end_sample=2*sr, gains_start=[0.1], gains_end=[0.2])])
+
+ # jump position with no interpolation -> fixed in block
+ assert (bf_to_states(rtime=Fraction(2), duration=Fraction(1), gain=0.3,
+ jumpPosition=JumpPosition(flag=True, interpolationLength=Fraction(0))) ==
+ [FixedGains(start_sample=2*sr, end_sample=3*sr, gains=[0.3])])
+
+ # jump position with some interpolation -> interpolated for part of block, constant for rest
+ assert (bf_to_states(rtime=Fraction(3), duration=Fraction(1), gain=0.4,
+ jumpPosition=JumpPosition(flag=True, interpolationLength=Fraction(1, 2))) ==
+ [InterpGains(start_sample=3*sr, end_sample=3.5*sr, gains_start=[0.3], gains_end=[0.4]),
+ FixedGains(start_sample=3.5*sr, end_sample=4*sr, gains=[0.4])])
+
+ # jump position with no interpolation length -> fixed in block
+ assert (bf_to_states(rtime=Fraction(4), duration=Fraction(1), gain=0.5, jumpPosition=JumpPosition(flag=True)) ==
+ [FixedGains(start_sample=4*sr, end_sample=5*sr, gains=[0.5])])
+
+ # gap between blocks -> fixed for whole block (same as first)
+ assert (bf_to_states(rtime=Fraction(6), duration=Fraction(1), gain=0.6) ==
+ [FixedGains(start_sample=6*sr, end_sample=7*sr, gains=[0.6])])
diff --git a/ear/core/objectbased/test/test_zone.py b/ear/core/objectbased/test/test_zone.py
new file mode 100644
index 00000000..cfb1d1f0
--- /dev/null
+++ b/ear/core/objectbased/test/test_zone.py
@@ -0,0 +1,154 @@
+import itertools
+import numpy as np
+import numpy.testing as npt
+
+from ..zone import ZoneExclusionDownmix
+from ... import bs2051
+
+
+def exclusions_to_try(layout):
+ nchannels = len(layout.channels)
+ if nchannels <= 10:
+ for excluded in itertools.product([0, 1], repeat=nchannels):
+ excluded = np.array(excluded, dtype=bool)
+ yield excluded
+ else:
+ yield np.zeros(nchannels, dtype=bool)
+ yield np.ones(nchannels, dtype=bool)
+
+ for i in range(nchannels):
+ excluded = np.ones(nchannels, dtype=bool)
+ excluded[i] = False
+ yield excluded
+
+ for excluded in np.random.randint(2, size=(1000, nchannels), dtype=bool):
+ yield excluded
+
+
+def test_zone_excl(any_layout):
+ zep = ZoneExclusionDownmix(any_layout)
+
+ for excluded in exclusions_to_try(any_layout):
+ downmix = zep.downmix_for_excluded(excluded)
+
+ # does not downmix to excluded channels
+ if not np.all(excluded):
+ excluded_cols = downmix[:, excluded]
+ assert np.all(excluded_cols == 0)
+
+ # all channels are rendered somewhere
+ npt.assert_allclose(np.sum(downmix, axis=1), 1.0)
+
+
+def check(layout, zep, from_channel, to_channels):
+ idx_norm = layout.channel_names.index
+
+ def idx_sym(name):
+ return idx_norm(name) if name.endswith(("000", "180")) else idx_norm(name.replace("-", "+")
+ if "-" in name
+ else name.replace("+", "-"))
+
+ for idx in idx_sym, idx_norm:
+ groups = zep.channel_groups[idx(from_channel)]
+ groups_set = [set(group) for group in groups]
+
+ to_channels_idx = [{idx(channel) for channel in group} for group in to_channels]
+
+ assert groups_set == to_channels_idx
+
+
+def test_450():
+ layout = bs2051.get_layout("4+5+0").without_lfe
+ zep = ZoneExclusionDownmix(layout)
+
+ check(layout, zep, "M+000", [
+ ["M+000"],
+ ["M+030", "M-030"],
+ ["M+110", "M-110"],
+ ["U+030", "U-030"],
+ ["U+110", "U-110"],
+ ])
+
+ check(layout, zep, "M-030", [
+ ["M-030"],
+ ["M+000"],
+ ["M+030"],
+ ["M-110"],
+ ["M+110"],
+ ["U-030"],
+ ["U+030"],
+ ["U-110"],
+ ["U+110"],
+ ])
+
+ check(layout, zep, "M-110", [
+ ["M-110"],
+ ["M+110"],
+ ["M-030"],
+ ["M+000"],
+ ["M+030"],
+ ["U-110"],
+ ["U+110"],
+ ["U-030"],
+ ["U+030"],
+ ])
+
+ check(layout, zep, "M-110", [
+ ["M-110"],
+ ["M+110"],
+ ["M-030"],
+ ["M+000"],
+ ["M+030"],
+ ["U-110"],
+ ["U+110"],
+ ["U-030"],
+ ["U+030"],
+ ])
+
+ check(layout, zep, "U-030", [
+ ["U-030"],
+ ["U+030"],
+ ["U-110"],
+ ["U+110"],
+ ["M-030"],
+ ["M+000"],
+ ["M+030"],
+ ["M-110"],
+ ["M+110"],
+ ])
+
+ check(layout, zep, "U-110", [
+ ["U-110"],
+ ["U+110"],
+ ["U-030"],
+ ["U+030"],
+ ["M-110"],
+ ["M+110"],
+ ["M-030"],
+ ["M+000"],
+ ["M+030"],
+ ])
+
+
+def test_9103():
+ layout = bs2051.get_layout("9+10+3").without_lfe
+ zep = ZoneExclusionDownmix(layout)
+
+ # just check upper/lower behaviour -- all speakers should take precedence
+ # over lower
+ check(layout, zep, "M+000", [
+ ['M+000'],
+ ['M+030', 'M-030'],
+ ['M+060', 'M-060'],
+ ['M+090', 'M-090'],
+ ['M+135', 'M-135'],
+ ['M+180'],
+ ['U+000'],
+ ['U+045', 'U-045'],
+ ['U+090', 'U-090'],
+ ['U+135', 'U-135'],
+ ['U+180'],
+ ['T+000'],
+ ['B+000'],
+ ['B+045', 'B-045'],
+ ])
diff --git a/ear/core/objectbased/zone.py b/ear/core/objectbased/zone.py
new file mode 100644
index 00000000..f0a5894f
--- /dev/null
+++ b/ear/core/objectbased/zone.py
@@ -0,0 +1,189 @@
+import numpy as np
+
+
+class ZoneExclusionDownmix(object):
+ """Calculate downmix coefficients to route output away from a given set of
+ loudspeakers.
+
+ For each channel, this stores a list of groups of other channels, sorted in
+ priority order. The downmix matrix generated is such that the energy from
+ each excluded channel is distributed between the non-excluded channels in
+ the highest priority group containing at least one non-excluded channel.
+
+ See calc_key in __init__ for the determination of the priority of each
+ channel, and therefore the overall behaviour.
+ """
+
+ def __init__(self, layout):
+ assert not any(channel.is_lfe for channel in layout.channels), \
+ "lfe channel passed to zone exclusion panner"
+
+ self.layout = layout
+ self.num_channels = len(self.layout.channels)
+
+ epsilon = 1e-6
+
+ def sign(x):
+ """sign function with some tolerance around zero"""
+ if x > epsilon:
+ return 1
+ elif x < -epsilon:
+ return -1
+ else:
+ return 0
+
+ # priority when moving between layers; prefer to move up before down
+ layer_prio = np.array([[0, 1, 2, 3], # B
+ [3, 0, 1, 2], # M
+ [3, 2, 0, 1], # U
+ [3, 2, 1, 0]] # T
+ )
+
+ def layer(channel):
+ """layer number of a channel"""
+ elevation = channel.polar_nominal_position.elevation
+
+ if elevation < -10:
+ return 0
+ elif elevation < 10:
+ return 1
+ elif elevation < 75:
+ return 2
+ else:
+ return 3
+
+ def unique_groups(groups):
+ """turn a list of (key, value) with duplicate keys into a list of
+ (key, [value]) with unique keys; keys are tuples and are compared
+ with some tolerance
+ """
+ uniq_groups = []
+ for key, value in groups:
+ for group_key, group_channels in uniq_groups:
+ if all(np.abs(key_item - group_key_item) < epsilon
+ for key_item, group_key_item in zip(key, group_key)):
+ group_channels.append(value)
+ break
+ else:
+ uniq_groups.append((key, [value]))
+
+ return uniq_groups
+
+ def ordered_values(groups):
+ """turn a list of (key, [value]) into a list of [value] sorted by key"""
+ return [np.array(values)
+ for _key, values
+ in sorted(groups, key=lambda group: group[0])]
+
+ def calc_key(from_channel, to_channel):
+ """calculate a key for this channel; channels with a lower key
+ (lexicographically) have a higher priority. If two channels
+ have the same key, then the energy may be split between them
+ """
+ # prefer channels on the same layer (see layer_prio above)
+ layer_priority = layer_prio[layer(from_channel), layer(to_channel)]
+
+ # prefer to keep sources behind/in front of the listener; this
+ # results in less extreme front/back movement when one side
+ # (left/right) is excluded
+ front_back_change = np.abs(sign(from_channel.nominal_position[1]) - sign(to_channel.nominal_position[1]))
+
+ # prefer closer speakers
+ cart_dist = np.linalg.norm(from_channel.nominal_position - to_channel.nominal_position)
+
+ # break ties by the front/back distance; this eliminates
+ # splitting that is not symmetrical around +x or +y
+ front_back_dist = np.abs(from_channel.nominal_position[1] - to_channel.nominal_position[1])
+
+ return (layer_priority, front_back_change, cart_dist, front_back_dist)
+
+ self.channel_groups = []
+ for i, from_channel in enumerate(layout.channels):
+ groups = [(calc_key(from_channel, to_channel), j)
+ for j, to_channel in enumerate(layout.channels)]
+
+ uniq_groups = unique_groups(groups)
+ channel_groups_for_i = ordered_values(uniq_groups)
+ assert channel_groups_for_i[0] == [i], "channel should always be mapped to itself if possible"
+
+ self.channel_groups.append(channel_groups_for_i)
+
+ def downmix_for_excluded(self, excluded):
+ """Calculate a downmix matrix for a given set of excluded channels.
+
+ Parameters:
+ excluded (array of n bool): If excluded[i], channel i is excluded.
+
+ Returns:
+ array of (n, n): Downmix matrix. M[i,j] is the coefficient from
+ channel i to channel j.
+ """
+ excluded = np.asarray(excluded, dtype=bool)
+ assert excluded.shape == (self.num_channels,)
+
+ if np.all(excluded) or np.all(~excluded):
+ return np.eye(self.num_channels)
+
+ downmix = np.zeros((self.num_channels, self.num_channels))
+
+ for i, groups in enumerate(self.channel_groups):
+ for group in groups:
+ if not np.all(excluded[group]):
+ not_excluded = group[~excluded[group]]
+ downmix[i, not_excluded] = 1.0 / len(not_excluded)
+ break
+ else:
+ assert False # pragma: no cover
+
+ return downmix
+
+
+def show_downmix(layout, downmix):
+ import matplotlib.pyplot as plt
+ from mpl_toolkits.mplot3d.art3d import Line3DCollection
+ import mpl_toolkits.mplot3d.art3d # noqa
+ import itertools
+ from .geom import cart
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection='3d', aspect="equal")
+
+ props_iter = itertools.cycle(plt.rcParams["axes.prop_cycle"])
+
+ for in_channel, downmix_row in enumerate(downmix):
+ if np.any(downmix_row[np.arange(len(downmix_row)) != in_channel]):
+ props = next(props_iter)
+ else:
+ props = dict(color="#606060")
+
+ elevation = layout.channels[in_channel].polar_nominal_position.elevation
+ if elevation > 70:
+ marker = '*'
+ elif elevation > 10:
+ marker = '^'
+ elif elevation > -10:
+ marker = 'o'
+ else:
+ marker = 'v'
+
+ ax.scatter3D(*layout.channels[in_channel].nominal_position, s=50, alpha=0.7, marker=marker, **props)
+
+ lines = Line3DCollection([[layout.channels[in_channel].norm_position,
+ layout.channels[out_channel].norm_position]
+ for out_channel, coeff in enumerate(downmix_row)
+ if coeff != 0.0],
+ **props)
+ ax.add_collection3d(lines)
+
+ # draw layer rings
+ elevations = {channel.polar_nominal_position.elevation for channel in layout.channels}
+ el_lines = Line3DCollection([cart(np.linspace(0, 360, 200), el, 1)
+ for el in elevations],
+ color="#000000", alpha=0.2)
+ ax.add_collection3d(el_lines)
+
+ ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1)
+ ax.set_xlabel("X"); ax.set_ylabel("Y"); ax.set_zlabel("Z")
+
+ ax.view_init(elev=30, azim=-55)
+ return fig, ax
diff --git a/ear/core/plot_point_source.py b/ear/core/plot_point_source.py
new file mode 100644
index 00000000..bbcfd77a
--- /dev/null
+++ b/ear/core/plot_point_source.py
@@ -0,0 +1,124 @@
+import numpy as np
+from . import bs2051, point_source, layout
+
+
+def plot_triplet(tri, ax, color=None):
+ import matplotlib.colors as colors
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
+
+ tri = Poly3DCollection([tri.positions])
+
+ if color is None:
+ color = colors.rgb2hex(np.random.rand(3))
+
+ tri.set_color(color)
+ tri.set_edgecolor('k')
+ ax.add_collection3d(tri)
+
+
+def plot_virtual_ngon(ngon, ax):
+ import matplotlib.colors as colors
+
+ color = colors.rgb2hex(np.random.rand(3))
+ for tri in ngon.regions:
+ plot_triplet(tri, ax, color)
+
+
+def plot_quad(quad, ax):
+ import matplotlib.colors as colors
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
+
+ quad_poly = Poly3DCollection([quad.positions[quad.order]])
+
+ color = colors.rgb2hex(np.random.rand(3))
+
+ quad_poly.set_color(color)
+ quad_poly.set_edgecolor('k')
+ ax.add_collection3d(quad_poly)
+
+
+def plot_stereo_pan(stereo, ax):
+ from mpl_toolkits.mplot3d.art3d import Line3DCollection
+
+ line = Line3DCollection(np.array([[[1, 0, 0], [-1, 0, 0]]]))
+ line.set_edgecolor('k')
+ ax.add_collection3d(line)
+
+
+plot_by_type = {
+ point_source.Triplet: plot_triplet,
+ point_source.VirtualNgon: plot_virtual_ngon,
+ point_source.QuadRegion: plot_quad,
+ point_source.StereoPanDownmix: plot_stereo_pan,
+}
+
+
+def plot_triangulation(point_source_panner):
+ import matplotlib.pyplot as plt
+ import mpl_toolkits.mplot3d.art3d # noqa
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection='3d', aspect=1)
+
+ if isinstance(point_source_panner, point_source.PointSourcePannerDownmix):
+ point_source_panner = point_source_panner.psp
+
+ for region in point_source_panner.regions:
+ if type(region) in plot_by_type:
+ plot_by_type[type(region)](region, ax)
+
+ ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1)
+ ax.set_xlabel("X"); ax.set_ylabel("Y"); ax.set_zlabel("Z")
+
+ return fig, ax
+
+
+def show_triangulation(point_source_panner):
+ import matplotlib.pyplot as plt
+ plot_triangulation(point_source_panner)
+ plt.show()
+
+
+def get_arg_parser():
+ import argparse
+ import json
+ parser = argparse.ArgumentParser(description="Plot point source configuration for a layout.")
+ parser.add_argument("--layout", choices=bs2051.layout_names, required=True)
+ parser.add_argument("--speakers", type=argparse.FileType('r'), metavar="speakers_file",
+ help="YAML format speakers file")
+ parser.add_argument("--options", type=json.loads, default="{}", help="JSON-format configuration options.") # noqa: P103
+ try:
+ import matplotlib
+ parser.add_argument("--mpl_backend",
+ choices=matplotlib.rcsetup.interactive_bk, default="TkAgg",
+ help="matplotlib backend to use. default: %(default)s")
+ except ImportError:
+ parser.add_argument("--mpl_backend", default="TkAgg",
+ help="matplotlib backend to use (install matplotlib to seee choices; default: %(default)s).")
+
+ return parser
+
+
+def main():
+ parser = get_arg_parser()
+ args = parser.parse_args()
+
+ try:
+ import matplotlib
+ except ImportError:
+ parser.error("matplotlib must be installed to use this program")
+
+ matplotlib.use(args.mpl_backend)
+
+ spkr_layout = bs2051.get_layout(args.layout)
+
+ if args.speakers is not None:
+ speakers = layout.load_speakers(args.speakers)
+ spkr_layout, _upmix = spkr_layout.with_speakers(speakers)
+
+ config = point_source.configure(spkr_layout.without_lfe, **args.options)
+ show_triangulation(config)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ear/core/point_source.py b/ear/core/point_source.py
new file mode 100644
index 00000000..61ac5ecd
--- /dev/null
+++ b/ear/core/point_source.py
@@ -0,0 +1,506 @@
+import numpy as np
+import scipy.spatial
+from attr import attrs, attrib, evolve
+from .util import as_array, has_shape
+from .geom import ngon_vertex_order, PolarPosition
+from .layout import Channel
+from ..options import OptionsHandler
+from . import bs2051
+
+
+class RegionHandler(object):
+ """An interface for objects that can calculate gains for some positions,
+ e.g. a triangle of loudspeakers.
+
+ Attributes:
+ output_channels (array of int): The channel numbers of the values
+ returned by handle.
+ """
+ __slots__ = ()
+
+ def handle(self, position):
+ """Try to calculate gains for a given position.
+
+ Args:
+ position (array of 3 doubles): Cartesian source position.
+
+ Returns:
+ Array of n doubles if successful; None otherwise. The value at
+ index i corresponds to the gain for channel output_channels[i], so
+ if an array is returned it must have the same size as
+ output_channels.
+ """
+ raise NotImplementedError() # pragma: no cover
+
+ def handle_remap(self, position, nchannels):
+ """Call handle, and map the output to the channels specified in
+ output_channels.
+
+ Args:
+ position (array of 3 doubles): Cartesian source position.
+ nchannels (int): Size of the output array.
+
+ Returns:
+ Array of nchannels doubles if successful; None otherwise.
+ """
+ pv = self.handle(position)
+
+ if pv is not None:
+ out = np.zeros(nchannels)
+ out[self.output_channels] = pv
+ return out
+
+
+@attrs(slots=True)
+class Triplet(RegionHandler):
+ """Region handler representing a triplet of loudspeakers, implementing VBAP.
+
+ This is implemented such that if handle(pos) returns array x:
+
+ - np.dot(x, positions) is collinear with pos
+ = x[i] >= 0 for all i
+ - norm(x) == 1
+
+ Note that the positions are *not* normalised, as this is not always
+ desirable.
+
+ Attributes:
+ output_channels: see RegionHandler.
+ positions (array of (3,3) doubles): Cartesian positions of the three
+ speakers; index order is speaker, axis.
+ """
+ output_channels = attrib(convert=as_array(dtype=int), validator=has_shape(3))
+ positions = attrib(convert=as_array(dtype=float), validator=has_shape(3, 3))
+
+ _basis = attrib(init=False, cmp=False, repr=False)
+
+ def __attrs_post_init__(self):
+ self._basis = np.linalg.inv(self.positions)
+
+ def handle(self, position):
+ pv = np.dot(position, self._basis)
+
+ epsilon = -1e-11
+ if pv[0] >= epsilon and pv[1] >= epsilon and pv[2] >= epsilon:
+ pv /= np.linalg.norm(pv)
+ pv.clip(0, 1, out=pv) # make sure all values are positive
+
+ return pv
+
+
+@attrs(slots=True)
+class VirtualNgon(RegionHandler):
+ """Region handler representing n real loudspeakers and a central virtual
+ loudspeaker, whose gain is distributed to the real loudspeakers.
+
+ Triplet regions are formed between the virtual speaker and pairs of real
+ speakers on the edge of the ngon. Any gain sent to the virtual speaker is
+ multiplied by centre_downmix and summed into the gains for the real
+ loudspeakers,which are then normalised.
+
+ Attributes:
+ output_channels: see RegionHandler.
+ positions (array of (n,3) doubles): Cartesian positions of the n
+ loudspeakers.
+ centre_position (array of 3 doubles): Cartesian position of the central
+ virtual loudspeaker.
+ centre_downmix (array of n doubles): Downmix coefficients for
+ distributing gains from the centre virtual loudspeaker to the
+ loudspeakers defined by positions.
+ """
+ output_channels = attrib(convert=as_array(dtype=int), validator=has_shape(None))
+ positions = attrib(convert=as_array(dtype=float), validator=has_shape(None, 3))
+ centre_position = attrib(convert=as_array(dtype=float), validator=has_shape(3))
+ centre_downmix = attrib(convert=as_array(dtype=float), validator=has_shape(None))
+
+ regions = attrib(init=False, cmp=False, repr=False)
+
+ def __attrs_post_init__(self):
+ n = len(self.output_channels)
+ assert n == len(self.positions) == len(self.centre_downmix)
+
+ order = ngon_vertex_order(self.positions)
+
+ self.regions = []
+ for i in range(n):
+ j = (i + 1) % n
+
+ tri_positions = np.array([
+ self.positions[order[i]],
+ self.positions[order[j]],
+ self.centre_position])
+ tri_channels = [order[i], order[j], n]
+
+ self.regions.append(Triplet(tri_channels, tri_positions))
+
+ def handle(self, position):
+ for region in self.regions:
+ pv = region.handle_remap(position, len(self.centre_downmix) + 1)
+
+ if pv is not None:
+ # downmix the last channel containing the virtual centre
+ # speaker into the real speakers, and renormalise
+ pv[:-1] += pv[-1] * self.centre_downmix
+ pv = pv[:-1]
+
+ pv /= np.linalg.norm(pv)
+
+ return pv
+
+
+@attrs(slots=True)
+class QuadRegion(RegionHandler):
+
+ output_channels = attrib(convert=as_array(dtype=int), validator=has_shape(4))
+ positions = attrib(convert=as_array(dtype=float), validator=has_shape(4, 3))
+
+ order = attrib(default=None)
+ pan_x = attrib(default=None)
+ pan_y = attrib(default=None)
+
+ @classmethod
+ def pan_axis(cls, spk_positions):
+ a, b, c, d = spk_positions
+
+ poly = np.array([
+ np.cross(b-a, c-d),
+ np.cross(a, c-d) + np.cross(b-a, d),
+ np.cross(a, d),
+ ])
+
+ def handle(position):
+ roots = np.roots(np.dot(poly, position))
+
+ epsillon = 1e-10
+ for root in roots:
+ if (np.abs(np.imag(root)) < epsillon and
+ -epsillon < np.real(root) < 1 + epsillon):
+ return np.clip(np.real(root), 0, 1)
+
+ return handle
+
+ def __attrs_post_init__(self):
+ self.order = ngon_vertex_order(self.positions)
+ self.pan_x = self.pan_axis(self.positions[self.order])
+ self.pan_y = self.pan_axis(self.positions[self.order][[1, 2, 3, 0]])
+
+ def handle(self, position):
+ x = self.pan_x(position)
+ y = self.pan_y(position)
+
+ if x is None or y is None:
+ return
+
+ pvs = np.zeros(4)
+ pvs[self.order] = np.array([
+ (1-x) * (1-y),
+ x * (1-y),
+ x * y,
+ (1-x) * y,
+ ])
+
+ if pvs.dot(self.positions).dot(position) <= 0:
+ return
+
+ pvs /= np.linalg.norm(pvs)
+
+ return pvs
+
+
+@attrs(slots=True)
+class StereoPanDownmix(RegionHandler):
+ """Stereo panning region handler.
+
+ This implements a panning function similar to 0+5+0 with a BS.775 downmix,
+ with corrected position and energy.
+
+ Attributes:
+ left_channel (int): Index of the left output channel.
+ right_channel (int): Index of the right output channel.
+ """
+ left_channel = attrib(convert=int)
+ right_channel = attrib(convert=int)
+
+ psp = attrib(default=None)
+
+ @property
+ def output_channels(self):
+ return np.array((self.left_channel, self.right_channel))
+
+ def __attrs_post_init__(self):
+ layout = bs2051.get_layout("0+5+0").without_lfe
+ assert layout.channel_names == ["M+030", "M-030", "M+000", "M+110", "M-110"]
+
+ self.psp = configure(layout)
+
+ def handle(self, position):
+ # downmix as in ITU-R BS.775, but with the centre downmix adjusted to
+ # preserve the velocity vector rather than the output power
+ downmix = [
+ [1.0000, 0.0000, np.sqrt(3) / 3, np.sqrt(0.5), 0.0000],
+ [0.0000, 1.0000, np.sqrt(3) / 3, 0.0000, np.sqrt(0.5)],
+ ]
+
+ # pan with 0+5+0, downmix and power normalise
+ pv = self.psp.handle(position)
+ pv_dmix = np.dot(downmix, pv)
+ pv_dmix /= np.linalg.norm(pv_dmix)
+
+ # vary the output level by the balance between the front and rear
+ # loudspeakers; 0dB at the front to -3dB at the back
+ front = np.max(pv[[0, 1, 2]])
+ back = np.max(pv[[3, 4]])
+
+ pv_dmix *= 0.5 ** (0.5 * back / (front + back))
+
+ return pv_dmix
+
+
+@attrs(slots=True)
+class PointSourcePanner(object):
+ """Wrapper around multiple regions.
+
+ Attributes:
+ regions (list of RegionHandler): Regions used to handle a position.
+ num_channels (int): Number of output channels; this is computed from
+ the output channels of the regions if not provided.
+ """
+ regions = attrib()
+ num_channels = attrib(default=None)
+
+ def _num_required_channels(self):
+ return max(np.max(region.output_channels) for region in self.regions) + 1
+
+ def __attrs_post_init__(self):
+ if self.num_channels is None:
+ self.num_channels = self._num_required_channels()
+ else:
+ assert self.num_channels >= self._num_required_channels(), "not enough channels"
+
+ def handle(self, position):
+ """Calculate gains for position using one of self.regions.
+
+ Args:
+ position (array of 3 doubles): Cartesian source position.
+
+ Returns:
+ Array of self.num_channels doubles if a region was found to handle
+ this position; None otherwise.
+ """
+ for region in self.regions:
+ pv = region.handle_remap(position, self.num_channels)
+ if pv is not None:
+ return pv
+
+
+@attrs(slots=True)
+class PointSourcePannerDownmix(object):
+ """Wrapper around a point source panner with an additional downmix.
+
+ Attributes:
+ psp (PointSourcePanner): Inner point source panner.
+ downmix (array of (n,m)): Downmix matrix from m inputs to n outputs.
+ """
+ psp = attrib()
+ downmix = attrib()
+
+ @property
+ def num_channels(self):
+ return self.downmix.shape[0]
+
+ def handle(self, position):
+ pv = self.psp.handle(position)
+ if pv is not None:
+ pv = np.dot(self.downmix, pv)
+ pv /= np.linalg.norm(pv)
+ return pv
+
+
+def _configure_stereo(layout):
+ """Configure a point source panner assuming an 0+2+0 layout."""
+ left_channel = layout.channel_names.index("M+030")
+ right_channel = layout.channel_names.index("M-030")
+
+ panner = StereoPanDownmix(left_channel=left_channel, right_channel=right_channel)
+
+ return PointSourcePanner([panner])
+
+
+def extra_pos_vertical_nominal(layout):
+ """Generate extra loudspeaker positions to fill gaps in layers.
+
+ Args:
+ layout (layout.Layout): Original layout.
+
+ Returns:
+ - list of extra channels (layout.Channel).
+ - downmix matrix to mix the extra channel outputs to the real channels
+ """
+ extra_channels = []
+ downmix = list(np.identity(len(layout.channels)))
+
+ pos = np.rec.array([(channel.polar_nominal_position.azimuth, channel.polar_nominal_position.elevation,
+ channel.polar_position.azimuth, channel.polar_position.elevation)
+ for channel in layout.channels],
+ dtype=[("nominal_az", float), ("nominal_el", float),
+ ("real_az", float), ("real_el", float)])
+
+ mid = (-10 <= pos.nominal_el) & (pos.nominal_el <= 10)
+
+ layers = [(-30, -70, -10), (30, 10, 70)]
+ for layer_nominal_el, layer_lb, layer_ub in layers:
+ layer = (layer_lb <= pos.nominal_el) & (pos.nominal_el <= layer_ub)
+
+ # for each loudspeaker in the mid layer that has an azimuth greater
+ # than az_limit, add a virtual speaker directly above/below it at the
+ # elevation of the current layer, which is downmixed directly to the
+ # mid layer loudspeaker. az_limit is set to the range of azimuths in
+ # the current layer, with some space added to prevent fast vertical
+ # source movements when sources move horizontally. If there are no
+ # channels on this layer then a copy of all mid layer speakers is made.
+ if np.any(layer):
+ az_range = np.max(np.abs(pos.nominal_az[layer]))
+ az_limit = az_range + 40
+ layer_real_el = np.mean(pos[layer].real_el)
+ else:
+ az_limit = 0.0
+ layer_real_el = layer_nominal_el
+
+ for mid_channel, mid_pos in zip(np.where(mid)[0], pos[mid]):
+ epsilon = 1e-5
+ if np.abs(mid_pos.nominal_az) >= az_limit - epsilon:
+ extra_channels.append(Channel(name="extra",
+ polar_position=PolarPosition(
+ azimuth=mid_pos.real_az,
+ elevation=layer_real_el,
+ distance=1.0),
+ polar_nominal_position=PolarPosition(
+ azimuth=mid_pos.nominal_az,
+ elevation=layer_nominal_el,
+ distance=1.0)))
+ downmix_row = np.zeros(len(layout.channels))
+ downmix_row[mid_channel] = 1
+ downmix.append(downmix_row)
+
+ return extra_channels, np.array(downmix).T
+
+
+def _convex_hull_facets(positions):
+ """Find the convex hull of a set of positions, with coplanar triangles
+ being merged into facets with any number of corners.
+
+ Args:
+ positions (array of nx3 floats): Vertex positions.
+
+ Returns:
+ list of sets of ints: Facets of the convex hull; each set represents a
+ facet and contains the indices of its corners in positions.
+ """
+ hull = scipy.spatial.ConvexHull(positions)
+
+ facets = []
+ for tri, equation in zip(hull.simplices, hull.equations):
+ for facet_eqn, facet_verts in facets:
+ if np.linalg.norm(facet_eqn - equation) < 1e-5:
+ facet_verts.update(tri)
+ break
+ else:
+ facets.append((equation, set(tri)))
+
+ return [verts for eqn, verts in facets]
+
+
+def _adjacent_verts(facets, vert):
+ """Find the adjacent vertices in a hull to the given vertex.
+
+ Args:
+ facets (list of sets of ints): Convex hull facets, each item represents
+ a facet, with the contents of the set being its vertex indices.
+ vert (int): Vertex index to find vertices adjacent to.
+
+ Returns:
+ set of ints: Vertices adjacent to `vert`.
+ """
+ return (set.union(*[facet_verts - set([vert])
+ for facet_verts in facets
+ if vert in facet_verts]) -
+ set([vert]))
+
+
+def _configure_full(layout):
+ # add some extra height speakers that are treated as real speakers until
+ # the downmix in PointSourcePannerDownmix
+ extra_channels, downmix = extra_pos_vertical_nominal(layout)
+ layout_extra = evolve(layout, channels=layout.channels + extra_channels)
+
+ # add some virtual speakers above and below that will be used as the centre
+ # speaker in a virtual ngon. No upper speaker is added for layouts with
+ # UH+180 as this speaker may actually be directly overhead, which may cause
+ # a step in the gains wrt the source position.
+ virtual_positions = [[0, 0, -1]]
+ if not ("T+000" in layout.channel_names or "UH+180" in layout.channel_names):
+ virtual_positions.append([0, 0, 1])
+
+ positions_nominal = np.concatenate((layout_extra.nominal_positions, virtual_positions))
+ positions_real = np.concatenate((layout_extra.norm_positions, virtual_positions))
+ virtual_verts = len(layout_extra.channels) + np.arange(len(virtual_positions))
+
+ facets = _convex_hull_facets(positions_nominal)
+
+ # Turn the facets into regions for the point source panner.
+ regions = []
+
+ # Facets adjacent to one of the virtual speakers are turned into virtual
+ # ngons, with an equal power downmix from the virtual speaker to the real
+ # speakers.
+ for virtual_vert in virtual_verts:
+ real_verts = np.fromiter(_adjacent_verts(facets, virtual_vert), int)
+ assert not set(real_verts).intersection(virtual_verts)
+
+ regions.append(VirtualNgon(
+ output_channels=real_verts,
+ positions=positions_real[real_verts],
+ centre_position=positions_real[virtual_vert],
+ centre_downmix=np.full(len(real_verts), 1.0 / np.sqrt(len(real_verts)))
+ ))
+
+ # Facets not adjacent to virtual speakers are turned into triplets or
+ # quads. In the supported layouts there are never facets with more
+ # vertices.
+ for facet_verts in facets:
+ if facet_verts.intersection(virtual_verts):
+ continue
+
+ facet_verts = np.fromiter(facet_verts, int)
+ if len(facet_verts) == 3:
+ regions.append(Triplet(output_channels=facet_verts,
+ positions=positions_real[facet_verts]))
+ elif len(facet_verts) == 4:
+ regions.append(QuadRegion(output_channels=facet_verts,
+ positions=positions_real[facet_verts]))
+ else:
+ assert False, "facets with more than 4 vertices are not supported"
+
+ return PointSourcePannerDownmix(PointSourcePanner(regions), downmix=downmix)
+
+
+configure_options = OptionsHandler()
+
+
+def configure(layout):
+ """Configure a point source panner given a loudspeaker layout.
+
+ Args:
+ layout (.layout.Layout): Loudspeaker layout.
+
+ Returns:
+ PointSourcePanner: point source panner configured to output channels in
+ the same order as layout.channels.
+ """
+ assert not any(channel.is_lfe for channel in layout.channels), \
+ "lfe channel passed to point source panner"
+
+ if layout.name == "0+2+0":
+ return _configure_stereo(layout)
+ else:
+ return _configure_full(layout)
diff --git a/ear/core/renderer.py b/ear/core/renderer.py
new file mode 100644
index 00000000..93a22e63
--- /dev/null
+++ b/ear/core/renderer.py
@@ -0,0 +1,91 @@
+import numpy as np
+from .objectbased.renderer import ObjectRenderer
+from .direct_speakers.renderer import DirectSpeakersRenderer
+from .scenebased.renderer import HOARenderer
+from ..options import SubOptions, OptionsHandler
+from .metadata_input import ObjectRenderingItem, DirectSpeakersRenderingItem, HOARenderingItem
+from .block_aligner import BlockAligner
+
+
+class Renderer(object):
+ """Renderer that supports all the available object types (currently only
+ objects).
+
+ Parameters:
+ layout (.layout.Layout): loudspeaker layout to render to
+ """
+
+ options = OptionsHandler(
+ object_renderer_opts=SubOptions(
+ handler=ObjectRenderer.options,
+ description="options for object based renderer",
+ ),
+ direct_speakers_opts=SubOptions(
+ handler=DirectSpeakersRenderer.options,
+ description="options for direct speakers renderer",
+ ),
+ hoa_renderer_opts=SubOptions(
+ handler=HOARenderer.options,
+ description="options for HOA renderer",
+ ),
+ )
+
+ @options.with_defaults
+ def __init__(self, layout, object_renderer_opts={}, direct_speakers_opts={}, hoa_renderer_opts={}):
+ self.block_aligner = BlockAligner(len(layout.channels))
+
+ self._object_renderer = ObjectRenderer(layout, **object_renderer_opts)
+ self._direct_speakers_renderer = DirectSpeakersRenderer(layout, **direct_speakers_opts)
+ self._hoa_renderer = HOARenderer(layout, **hoa_renderer_opts)
+
+ self.start_sample = 0
+
+ def set_rendering_items(self, rendering_items):
+ self._object_renderer.set_rendering_items([item for item in rendering_items
+ if isinstance(item, ObjectRenderingItem)])
+
+ self._direct_speakers_renderer.set_rendering_items([item for item in rendering_items
+ if isinstance(item, DirectSpeakersRenderingItem)])
+
+ self._hoa_renderer.set_rendering_items([item for item in rendering_items
+ if isinstance(item, HOARenderingItem)])
+
+ # XXX: check for unsupported types?
+
+ def render(self, sample_rate, samples):
+ """Render n samples.
+
+ Args:
+ sample_rate (int): Sample Rate.
+ samples (ndarray of (n, k) floats): k channels of input audio.
+
+ Note:
+ This may return fewer output samples than input samples in order to
+ compensate for processing delay; the first sample returned is
+ always output sample 0. Call `get_tail` after all input audio has
+ been passed to `render` to get the missing samples.
+
+ Returns:
+ ndarray of (m, l): m samples and l channels of output audio.
+ """
+ self.block_aligner.add(
+ self.start_sample - self._object_renderer.overall_delay,
+ self._object_renderer.render(sample_rate, self.start_sample, samples))
+
+ self.block_aligner.add(
+ self.start_sample,
+ self._direct_speakers_renderer.render(sample_rate, self.start_sample, samples))
+
+ self.block_aligner.add(
+ self.start_sample,
+ self._hoa_renderer.render(sample_rate, self.start_sample, samples))
+
+ self.start_sample += len(samples)
+
+ return self.block_aligner.get()
+
+ def get_tail(self, sample_rate, n_channels):
+ """Get an additional block of samples that completes the output."""
+ total_delay = self._object_renderer.overall_delay
+
+ return self.render(sample_rate, np.zeros((total_delay, n_channels)))
diff --git a/ear/core/renderer_common.py b/ear/core/renderer_common.py
new file mode 100644
index 00000000..5754e4f7
--- /dev/null
+++ b/ear/core/renderer_common.py
@@ -0,0 +1,286 @@
+import numpy as np
+import math
+from fractions import Fraction
+from attr import attrs, attrib
+from collections import deque
+import warnings
+
+
+def ceil(x):
+ """Ceiling function compatible with Fraction on both python 2 and 3 that
+ also passes through inf."""
+ if math.isinf(x):
+ return x
+ y = math.trunc(x)
+ if y < x:
+ y += 1
+ return y
+
+
+@attrs(slots=True, frozen=True)
+class ProcessingBlock(object):
+ """Time-bounded audio processing.
+
+ This applies some kind of audio effect between fractional sample numbers
+ start_sample and end_sample. The actual samples affected are those between
+ first_sample and last_sample.
+
+ This class deals with the timing of the blocks; the actual audio processing
+ is defined in sub-classes.
+ """
+
+ # fractional start and end samples; sample number s is affected if
+ # start_sample <= s < end_sample
+ start_sample = attrib()
+ end_sample = attrib()
+
+ # integer sample numbers of the first and last sample affected; sample
+ # number s is affected if first_sample <= s <= last_sample
+ first_sample = attrib(init=False, cmp=False)
+ last_sample = attrib(init=False, cmp=False)
+
+ @first_sample.default
+ def init_start_sample_round(self):
+ return ceil(self.start_sample)
+
+ @last_sample.default
+ def init_end_sample_round(self):
+ return ceil(self.end_sample)
+
+ def overlap(self, start_sample, num_samples):
+ """Calculate the overlap between this block and a block of samples.
+
+ Args:
+ start_sample (int): Index of the first sample in the block.
+ num_samples (int): Number of samples in the block.
+
+ Returns:
+ - slice: range of samples in this block (with 0 being the first
+ sample affected by this block) that the overlap covers. can be used
+ to index some internal state of this block.
+ - slice: range of samples in the block of samples (with 0 being the
+ first sample in the block) that the overlap covers. This can be
+ used to index the sample block.
+ """
+ end_sample = start_sample + num_samples
+
+ overlap_start_sample = max(start_sample, self.first_sample)
+ overlap_end_sample = min(end_sample, self.last_sample)
+
+ return (slice(overlap_start_sample - self.first_sample, overlap_end_sample - self.first_sample),
+ slice(overlap_start_sample - start_sample, overlap_end_sample - start_sample))
+
+
+@attrs(slots=True, frozen=True)
+class FixedGains(ProcessingBlock):
+ """Take a single input channel, apply n gains and sum into n output channels."""
+
+ gains = attrib()
+
+ def process(self, start_sample, input_samples, output_samples):
+ ovl_state, ovl_samples = self.overlap(start_sample, len(input_samples))
+
+ output_samples[ovl_samples] += input_samples[ovl_samples, np.newaxis] * self.gains[np.newaxis]
+
+
+@attrs(slots=True, frozen=True)
+class InterpGains(ProcessingBlock):
+ """Take a single input channel, apply n linearly interpolated gains and sum into n output channels.
+
+ Attributes:
+ gains_start (array of n floats): Gains to be applied at time start_sample.
+ gains_end (array of n floats): Gains to be applied at time end_sample.
+ """
+
+ gains_start = attrib()
+ gains_end = attrib()
+
+ # interpolation coefficients: ramp from 0 to 1 between start_sample and
+ # end_sample, sampled for each sample in range first_sample:last_sample
+ _interp_p = attrib(init=False, cmp=False)
+
+ @_interp_p.default
+ def init_interp_p(self):
+ # number of samples actually in the ramp
+ n = self.last_sample - self.first_sample
+
+ # avoid divide by 0 if there are no samples to apply to
+ if n == 0: return np.array([])
+
+ # value at first_sample and last_sample
+ start = float((self.first_sample - self.start_sample) / (self.end_sample - self.start_sample))
+ end = float((self.last_sample - self.start_sample) / (self.end_sample - self.start_sample))
+
+ return start + np.arange(n) * ((end - start) / n)
+
+ def process(self, start_sample, input_samples, output_samples):
+ ovl_state, ovl_samples = self.overlap(start_sample, len(input_samples))
+
+ if self.gains_start is not None:
+ input_fade_down = input_samples[ovl_samples] * (1.0 - self._interp_p[ovl_state])
+ output_samples[ovl_samples] += input_fade_down[:, np.newaxis] * self.gains_start[np.newaxis]
+
+ if self.gains_end is not None:
+ input_fade_up = input_samples[ovl_samples] * self._interp_p[ovl_state]
+ output_samples[ovl_samples] += input_fade_up[:, np.newaxis] * self.gains_end[np.newaxis]
+
+
+class BlockProcessingChannel(object):
+ """Given a source of metadata, and a method for turning that metadata into
+ audio processing blocks, apply the processing to an audio stream.
+
+ Args:
+ metadata_source (metadata_input.MetadataSource): Source of metadata to
+ pull from.
+ interpret_metadata (callable): Take a block from the metadata source
+ (e.g. an ObjectTypeMetadata) and produce some corresponding
+ ProcessingBlock objects to apply to the audio stream.
+ """
+
+ def __init__(self, metadata_source, interpret_metadata):
+ self.metadata_source = metadata_source
+ self.interpret_metadata = interpret_metadata
+
+ # queue of ProcessingBlock to apply to the audio stream; the first item
+ # is the currently active one
+ self.processing_queue = deque()
+
+ def _refil_processing_queue(self, sample_rate, start_sample=None):
+ """If processing_queue is empty, try to fill it up by pulling from the
+ metadata source and interpreting the result.
+
+ Args:
+ sample_rate (int): Sample rate, needed when interpreting metadata.
+ start_sample (int or None): Sample index of the first sample we are
+ about to process, to check that metadata has not appeared too late.
+ """
+ while not len(self.processing_queue):
+ block = self.metadata_source.get_next_block()
+
+ if block is None:
+ return
+
+ for new_state in self.interpret_metadata(sample_rate, block):
+ if start_sample is not None and new_state.first_sample < start_sample:
+ raise Exception("metadata underrun: metadata arrived after the samples that it would apply to")
+ self.processing_queue.append(new_state)
+
+ def process(self, sample_rate, start_sample, input_samples, output_samples):
+ """Process some samples.
+
+ Args:
+ sample_rate (int): Sample rate.
+ start_sample (int): Sample number of first sample.
+ input_samples (ndarray of float): Input samples.
+ output_samples (ndarray of float): Output samples.
+
+ Note:
+ The shape of `input_samples` and `output_samples` depends on the
+ shape accepted by the processing blocks used, but the samples must
+ be along the first axis.
+ """
+
+ end_sample = start_sample + len(input_samples)
+ self._refil_processing_queue(sample_rate, start_sample)
+
+ while len(self.processing_queue):
+ self.processing_queue[0].process(start_sample, input_samples, output_samples)
+
+ if self.processing_queue[0].last_sample < end_sample:
+ # processing ends before end of sample block; go to next processing block and apply that too
+ self.processing_queue.popleft()
+ self._refil_processing_queue(sample_rate)
+ elif self.processing_queue[0].last_sample == end_sample:
+ # processing ends at end of sample block; we've done with this processing block and this sample block
+ self.processing_queue.popleft()
+ break
+ else:
+ # processing ends after end of sample block; we're done with this sample block
+ break
+
+
+class InterpretTimingMetadata(object):
+ """Base class for Interpret*Metadata classes that knows how to determine
+ the start and end times of blocks and catch related errors.
+ """
+
+ def __init__(self):
+ self.__last_block_end = None
+
+ def block_start_end(self, block, block_time_in_block_format=True):
+ """Get the start and end time of a metadata block.
+
+ Note:
+ This keeps track of the last block end to detect overlapping
+ blocks, so must be called with all blocks in sequence.
+
+ Args:
+ block (TypeMetadata): Metadata block to determine timing for.
+ block_time_in_block_format (bool): Where do the rtime and duration
+ attributes live for this type?
+
+ Returns:
+ tuple:
+ block start time (Fraction): Time that the block starts at.
+ block end time (Fraction or inf): Time that the block ends at,
+ or inf if it has no end.
+ """
+ # determine object start and end time
+ if block.extra_data.object_start is not None:
+ object_start = block.extra_data.object_start
+ else:
+ object_start = Fraction(0)
+
+ if block.extra_data.object_duration is not None:
+ object_end = object_start + block.extra_data.object_duration
+ else:
+ object_end = np.inf
+
+ # pull out the block timing information
+ if block_time_in_block_format:
+ rtime, duration = block.block_format.rtime, block.block_format.duration
+ else:
+ rtime, duration = block.rtime, block.duration
+
+ # determine block start and end time
+ if rtime is not None and duration is not None:
+ block_start = object_start + rtime
+ block_end = block_start + duration
+
+ if block_end > object_end:
+ raise Exception("block {0.id} ends after object".format(block.block_format))
+ elif rtime is None and duration is None:
+ block_start, block_end = object_start, object_end
+ else:
+ raise Exception("rtime and duration must be used together.")
+
+ # check for overlapping blocks; this will also raise if there is more
+ # than one block without timing information or a mixture of blocks with
+ # and without
+ if self.__last_block_end is not None and block_start < self.__last_block_end:
+ raise Exception("overlapping blocks {0.id} detected".format(block.block_format))
+ self.__last_block_end = block_end
+
+ return block_start, block_end
+
+
+def is_lfe(frequency):
+ """Determine if a channel is an LFE channel from its frequency metadata.
+
+ This issues a warning if there is frequency information available but it is
+ not recognised as specifying an LFE channel.
+
+ Args:
+ frequency (Frequency): Frequency info from channelFormat.
+
+ Returns:
+ bool
+ """
+ if (frequency.lowPass is not None and
+ frequency.lowPass <= 200 and
+ frequency.highPass is None):
+ return True
+ else:
+ if frequency.lowPass is not None or frequency.highPass is not None:
+ warnings.warn("Not treating channel with frequency {!r} as LFE.".format(frequency))
+ return False
diff --git a/ear/core/scenebased/__init__.py b/ear/core/scenebased/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/core/scenebased/design.py b/ear/core/scenebased/design.py
new file mode 100644
index 00000000..b739216a
--- /dev/null
+++ b/ear/core/scenebased/design.py
@@ -0,0 +1,98 @@
+import numpy as np
+import warnings
+from .. import hoa
+from .. import point_source
+from ...options import OptionsHandler, Option, SubOptions
+
+
+class HOADecoderDesign(object):
+ """Design HOA deciders for a given layout.
+
+ Args:
+ layout (Layout): Loudspeaker layout to design decoders for.
+ """
+
+ options = OptionsHandler(
+ norm_mean_power=Option(default=True,
+ description="normalize the decoder"),
+ maxRE=Option(default=False,
+ description="apply maxRE weighting"),
+ maxRE_scale=Option(default="none",
+ description=("normalisation method for maxRE weights; "
+ "only relevant if maxRE is set and norm_mean_power is not. "
+ "options: none, speakers, components, order")),
+ point_source_opts=SubOptions(
+ handler=point_source.configure_options,
+ description="options for point source panner",
+ ),
+ )
+
+ @options.with_defaults
+ def __init__(self, layout, norm_mean_power, maxRE, maxRE_scale, point_source_opts):
+ self.psp = point_source.configure(layout, **point_source_opts)
+
+ self._initialised = False
+
+ self.norm_mean_power = norm_mean_power
+ self.maxRE = maxRE
+ self.maxRE_scale = maxRE_scale
+
+ def init_slow(self):
+ """Do the slow bits of the initialisation.
+
+ Optionally call this after this object has been created to speed up the
+ first call to design.
+ """
+ if not self._initialised:
+ self._initialised = True
+ self.points = hoa.load_points()
+ self.G_virt = hoa.allrad_calc_G_virt(self.points, self.psp.handle)
+
+ def design(self, type_metadata):
+ """Design a decoder matrix for the given HOA format.
+
+ Args:
+ type_metadata (HOATypeMetadata): HOA metadata.
+
+ Returns:
+ l, m decoder matrix from m HOA channels to l loudspeaker channels
+ """
+ self.init_slow()
+
+ if type_metadata.screenRef:
+ warnings.warn("screenRef for HOA is not implemented; ignoring")
+ if (type_metadata.extra_data.channel_frequency.lowPass is not None or
+ type_metadata.extra_data.channel_frequency.highPass is not None):
+ warnings.warn("frequency information for HOA is not implemented; ignoring")
+
+ n, m = np.array(type_metadata.orders), np.array(type_metadata.degrees)
+
+ norm = hoa.norm_functions[type_metadata.normalization]
+ decoder = hoa.allrad_design(self.points, self.psp.handle, n, m, norm, G_virt=self.G_virt)
+
+ # apply maxRE weights
+ if self.maxRE:
+ a_n = hoa.ApproxMaxRECoefficients(max(n))[n]
+
+ # various options proposed for scaling maxRE weights to preserve
+ # loudness; this is irrelevant if norm_mean_power is used.
+ if self.maxRE_scale == "speakers":
+ a_n *= np.sqrt(decoder.shape[0] / np.sum(a_n[n]**2))
+ elif self.maxRE_scale == "components":
+ a_n *= np.sqrt(len(n) / np.sum(a_n[n]**2))
+ elif self.maxRE_scale == "order":
+ a_n *= np.sqrt(max(n) / np.sum(a_n[n]**2))
+ else:
+ assert self.maxRE_scale == "none", "unknown maxRE_scale option {!r}".format(self.maxRE_scale)
+
+ decoder *= a_n[n]
+
+ # normalize the decoder so that the mean power is 1 by sampling over
+ # the sphere as in the allrad design function
+ if self.norm_mean_power:
+ az = -np.arctan2(self.points[:, 0], self.points[:, 1])
+ el = np.arctan2(self.points[:, 2], np.hypot(self.points[:, 0], self.points[:, 1]))
+ K_v = hoa.sph_harm(n[:, np.newaxis], m[:, np.newaxis], az[np.newaxis], el[np.newaxis], norm=norm)
+ decoder /= np.sqrt(np.mean(np.sum(np.dot(decoder, K_v) ** 2, axis=0)))
+
+ return decoder
diff --git a/ear/core/scenebased/renderer.py b/ear/core/scenebased/renderer.py
new file mode 100644
index 00000000..3577ff70
--- /dev/null
+++ b/ear/core/scenebased/renderer.py
@@ -0,0 +1,110 @@
+import numpy as np
+from attr import attrs, attrib
+from .design import HOADecoderDesign
+from ..renderer_common import BlockProcessingChannel, InterpretTimingMetadata, ProcessingBlock
+from ...options import OptionsHandler, SubOptions
+
+
+@attrs(slots=True, frozen=True)
+class FixedMatrix(ProcessingBlock):
+ """Take n input channels, apply a matrix, and sum into m output channels.
+
+ Attributes:
+ matrix (ndarray of shape (m, n)): Matrix from n input channels to m
+ output channels.
+ output_channels (index for ndarray): Channels in output to sum into.
+ """
+ matrix = attrib()
+ output_channels = attrib()
+
+ def process(self, start_sample, input_samples, output_samples):
+ ovl_state, ovl_samples = self.overlap(start_sample, len(input_samples))
+
+ output_samples[ovl_samples, self.output_channels] += np.dot(input_samples[ovl_samples], self.matrix.T)
+
+
+class InterpretHOAMetadata(InterpretTimingMetadata):
+ """Interpret a sequence of HOATypeMetadata, producing a sequence of ProcessingBlock.
+
+ Args:
+ design_decoder (callable): Called with HOATypeMetadata to design a decode matrix.
+ output_channels (index for ndarray): Output channels to sum into.
+ """
+
+ def __init__(self, design_decoder, output_channels):
+ super(InterpretHOAMetadata, self).__init__()
+ self.design_decoder = design_decoder
+ self.output_channels = output_channels
+
+ def __call__(self, sample_rate, block):
+ """Yield ProcessingBlock that apply the processing for a given HOATypeMetadata.
+
+ Args:
+ sample_rate (int): Sample rate to operate in.
+ block (HOATypeMetadata): Metadata to interpret.
+
+ Yields:
+ One ProcessingBlock object that apply gains for a single input channel.
+ """
+ start_time, end_time = self.block_start_end(block, block_time_in_block_format=False)
+
+ start_sample = sample_rate * start_time
+ end_sample = sample_rate * end_time
+
+ decoder = self.design_decoder(block)
+
+ yield FixedMatrix(start_sample, end_sample, decoder, self.output_channels)
+
+
+class HOARenderer(object):
+
+ options = OptionsHandler(
+ design_opts=SubOptions(handler=HOADecoderDesign.options,
+ description="options for decoder design"),
+ )
+
+ @options.with_defaults
+ def __init__(self, layout, design_opts):
+ self._decoder_design = HOADecoderDesign(layout.without_lfe, **design_opts)
+ self._output_channels = ~layout.is_lfe
+
+ self.block_processing_channels = []
+
+ def set_rendering_items(self, rendering_items):
+ """Set the rendering items to process.
+
+ Note:
+ Since this resets the internal state, this should normally be called
+ once before rendering is started. Dynamic modification of the
+ rendering items could be implemented though another API.
+
+ Args:
+ rendering_items (list of HOARenderingItem): Items to process.
+ """
+ self.block_processing_channels = [(item.track_indices,
+ BlockProcessingChannel(
+ item.metadata_source,
+ InterpretHOAMetadata(self._decoder_design.design,
+ self._output_channels)))
+ for item in rendering_items]
+
+ def render(self, sample_rate, start_sample, input_samples):
+ """Process n input samples to produce n output samples.
+
+ Args:
+ sample_rate (int): Sample rate.
+ start_sample (int): Index of the first sample in input_samples.
+ input_samples (ndarray of (k, k) float): Multi-channel input sample
+ block; there must be at least as many channels as referenced in the
+ rendering items.
+
+ Returns:
+ (ndarray of (n, l) float): l channels of output samples
+ corresponding to the l loudspeakers in layout.
+ """
+ output_samples = np.zeros((len(input_samples), len(self._output_channels)))
+
+ for channels, block_processing in self.block_processing_channels:
+ block_processing.process(sample_rate, start_sample, input_samples[:, channels], output_samples)
+
+ return output_samples
diff --git a/ear/core/screen_common.py b/ear/core/screen_common.py
new file mode 100644
index 00000000..b68d0007
--- /dev/null
+++ b/ear/core/screen_common.py
@@ -0,0 +1,88 @@
+from attr import attrs, attrib
+from ..common import CartesianScreen, PolarScreen
+from ..common import azimuth, elevation
+from .geom import local_coordinate_system
+import numpy as np
+
+
+@attrs(slots=True)
+class PolarEdges(object):
+ """Internal screen representation for scaling polar coordinates.
+
+ This stores the azimuths of the right and left edges, and the elevations of
+ the top and bottom edges.
+ """
+ left_azimuth = attrib()
+ right_azimuth = attrib()
+ bottom_elevation = attrib()
+ top_elevation = attrib()
+
+ @classmethod
+ def from_screen(cls, screen):
+ # Determine the Cartesian position, angle and size of the screen, then
+ # use that to determine the edge azimuths and elevations. The screen surface is given by
+ #
+ # centre + x * x_vec + y * y_vec
+ #
+ # for x and y in the range [-1, 1], where x=1, y=1 is the top right
+ # corner, and x=1, y=-1 is the bottom right corner.
+ centre = screen.centrePosition.as_cartesian_array()
+
+ if isinstance(screen, PolarScreen):
+ width = screen.centrePosition.distance * np.tan(np.radians(screen.widthAzimuth / 2.0))
+ height = width / screen.aspectRatio
+
+ axes = local_coordinate_system(screen.centrePosition.azimuth, screen.centrePosition.elevation)
+
+ x_vec = axes[0] * width
+ z_vec = axes[2] * height
+
+ elif isinstance(screen, CartesianScreen):
+ width = screen.widthX / 2.0
+ height = width / screen.aspectRatio
+
+ x_vec, z_vec = np.array([[width, 0.0, 0.0], [0.0, 0.0, height]])
+
+ else:
+ assert False
+
+ return cls(left_azimuth=azimuth(centre - x_vec),
+ right_azimuth=azimuth(centre + x_vec),
+ bottom_elevation=elevation(centre - z_vec),
+ top_elevation=elevation(centre + z_vec))
+
+
+@attrs(slots=True)
+class CartesianEdges(object):
+ """Internal screen representation for scaling cartesian coordinates.
+
+ This stores the Xs of the right and left edges, and the Zs of
+ the top and bottom edges.
+ """
+ left_X = attrib()
+ right_X = attrib()
+ bottom_Z = attrib()
+ top_Z = attrib()
+
+ @classmethod
+ def from_screen(cls, screen):
+ if isinstance(screen, PolarScreen):
+ centre_position = screen.centrePosition.as_cartesian_position()
+ width_x = (2.0 * screen.centrePosition.distance *
+ np.tan(np.radians(screen.widthAzimuth / 2.0)))
+ height_z = width_x / screen.aspectRatio
+ return cls(left_X=centre_position.X - width_x / 2.0,
+ right_X=centre_position.X + width_x / 2.0,
+ bottom_Z=centre_position.Z - height_z / 2.0,
+ top_Z=centre_position.Z + height_z / 2.0)
+
+ elif isinstance(screen, CartesianScreen):
+ height_z = screen.widthX / screen.aspectRatio
+
+ return cls(left_X=screen.centrePosition.X - screen.widthX / 2.0,
+ right_X=screen.centrePosition.X + screen.widthX / 2.0,
+ bottom_Z=screen.centrePosition.Z - height_z / 2.0,
+ top_Z=screen.centrePosition.Z + height_z / 2.0)
+
+ else:
+ assert False
diff --git a/ear/core/screen_edge_lock.py b/ear/core/screen_edge_lock.py
new file mode 100644
index 00000000..23db8c7e
--- /dev/null
+++ b/ear/core/screen_edge_lock.py
@@ -0,0 +1,42 @@
+from .geom import azimuth, elevation, cart
+from .screen_common import PolarEdges
+import numpy as np
+
+
+class ScreenEdgeLockHandler(object):
+
+ def __init__(self, reproduction_screen):
+ self.rep_screen_edges = (PolarEdges.from_screen(reproduction_screen)
+ if reproduction_screen is not None
+ else None)
+
+ def lock_to_screen_edge(self, az, el, screen_edge_lock):
+ if screen_edge_lock.horizontal == 'left':
+ az = self.rep_screen_edges.left_azimuth
+ if screen_edge_lock.horizontal == 'right':
+ az = self.rep_screen_edges.right_azimuth
+ if screen_edge_lock.vertical == 'top':
+ el = self.rep_screen_edges.top_elevation
+ if screen_edge_lock.vertical == 'bottom':
+ el = self.rep_screen_edges.bottom_elevation
+
+ return az, el
+
+ def should_modify_position(self, screen_edge_lock):
+ return (self.rep_screen_edges is not None and
+ (screen_edge_lock.horizontal is not None or
+ screen_edge_lock.vertical is not None))
+
+ def handle_vector(self, position, screen_edge_lock):
+ if self.should_modify_position(screen_edge_lock):
+ az, el, distance = azimuth(position), elevation(position), np.linalg.norm(position)
+ az, el = self.lock_to_screen_edge(az, el, screen_edge_lock)
+ return cart(az, el, distance)
+ else:
+ return position
+
+ def handle_az_el(self, az, el, screen_edge_lock):
+ if self.should_modify_position(screen_edge_lock):
+ return self.lock_to_screen_edge(az, el, screen_edge_lock)
+ else:
+ return az, el
diff --git a/ear/core/screen_scale.py b/ear/core/screen_scale.py
new file mode 100644
index 00000000..b12c0307
--- /dev/null
+++ b/ear/core/screen_scale.py
@@ -0,0 +1,40 @@
+from .geom import azimuth, elevation, cart
+import numpy as np
+from .screen_common import PolarEdges
+
+
+class PolarScreenScaler(object):
+ """Modifies the azimuth and elevation of a position independently,
+ preserving the distance:
+
+ - azimuth is interpolated between the screen edges and +-180
+ - elevation is interpolated between the screen edges and +-90
+ """
+
+ def __init__(self, reference_screen, reproduction_screen):
+ self.ref_screen_edges = PolarEdges.from_screen(reference_screen)
+ self.rep_screen_edges = PolarEdges.from_screen(reproduction_screen)
+
+ def scale_position(self, position):
+ az, el, distance = azimuth(position), elevation(position), np.linalg.norm(position)
+
+ new_az = np.interp(az,
+ (-180, self.ref_screen_edges.right_azimuth, self.ref_screen_edges.left_azimuth, 180),
+ (-180, self.rep_screen_edges.right_azimuth, self.rep_screen_edges.left_azimuth, 180))
+ new_el = np.interp(el,
+ (-90, self.ref_screen_edges.bottom_elevation, self.ref_screen_edges.top_elevation, 90),
+ (-90, self.rep_screen_edges.bottom_elevation, self.rep_screen_edges.top_elevation, 90))
+
+ return cart(new_az, new_el, distance)
+
+
+class ScreenScaleHandler(object):
+
+ def __init__(self, reproduction_screen):
+ self.reproduction_screen = reproduction_screen
+
+ def handle(self, position, screenRef, reference_screen):
+ if screenRef and self.reproduction_screen is not None:
+ return PolarScreenScaler(reference_screen, self.reproduction_screen).scale_position(position)
+ else:
+ return position
diff --git a/ear/core/subwoofer.py b/ear/core/subwoofer.py
new file mode 100644
index 00000000..020f0ce6
--- /dev/null
+++ b/ear/core/subwoofer.py
@@ -0,0 +1,81 @@
+import numpy as np
+
+
+def render_subwoofers(sub_positions, speaker_position):
+ """Generate upmix coefficients from a loudspeaker to up to 2 subwoofers.
+
+ Parameters:
+ sub_positions (ndarray of (n, 3)): subwoofer positions
+ speaker_position (ndarray of (3,)): loudspeaker position
+ Returns:
+ ndarray of (n,): upmix coefficient for each subwoofer.
+ """
+ if len(sub_positions) == 0:
+ return np.array([])
+ elif len(sub_positions) == 1:
+ return np.array([1.0])
+ elif len(sub_positions) == 2:
+ a, b = sub_positions
+
+ # projection the position onto the vector between a and b, scaled to 1 at b
+ vec = b - a
+ vec /= np.linalg.norm(vec) ** 2
+
+ p_b = np.clip(np.dot(vec, speaker_position - a), 0, 1)
+ p_a = 1.0 - p_b
+
+ return np.array([p_a, p_b])
+ else:
+ raise AssertionError("Invalid number of subwoofers: {}".format(len(sub_positions)))
+
+
+def subwoofer_upmix_matrix(layout):
+ """Generate an upmix matrix that maps from channels in layout.without_lfe
+ to channels in layout, using render_subwoofers to generate downmix
+ coefficients for LFE channels.
+
+ Parameters:
+ layout (..layout.Layout): layout with lfe channels to upmix to
+
+ Returns:
+ ndarray: upmix matrix M, such that if g is a vector of gains for each
+ channel in layout.without_lfe, M.dot(g) is a vector of gains for
+ each channel in layout
+ """
+ layout_no_sub = layout.without_lfe
+
+ sub_idx = [i for i, channel in enumerate(layout.channels) if channel.is_lfe]
+ sub_positions = layout.positions[sub_idx]
+
+ upmix = np.zeros((len(layout.channels), len(layout_no_sub.channels)))
+
+ for in_idx, channel in enumerate(layout_no_sub.channels):
+ out_idx = layout.channels.index(channel)
+ upmix[out_idx, in_idx] = 1.0
+ upmix[sub_idx, in_idx] = render_subwoofers(sub_positions, channel.position)
+
+ return upmix
+
+
+def lfe_downmix_matrix(layout):
+ """Generate a downmix matrix that sums channels in layout.without_lfe into
+ the lfe channels in layout.
+
+ Parameters:
+ layout (..layout.Layout): layout with lfe channels to upmix to
+
+ Returns:
+ ndarray: downmix matrix M, such that if g is a vector of gains for each
+ channel in layout.without_lfe, M.dot(g) is a vector of gains for
+ each channel in layout
+ """
+ layout_no_lfe = layout.without_lfe
+
+ downmix = np.zeros((len(layout.channels), len(layout_no_lfe.channels)))
+
+ lfe_positions = layout.positions[layout.is_lfe]
+
+ for idx, channel in enumerate(layout_no_lfe.channels):
+ downmix[layout.is_lfe, idx] = render_subwoofers(lfe_positions, channel.position)
+
+ return downmix
diff --git a/ear/core/test/__init__.py b/ear/core/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/core/test/conftest.py b/ear/core/test/conftest.py
new file mode 100644
index 00000000..b3601509
--- /dev/null
+++ b/ear/core/test/conftest.py
@@ -0,0 +1,13 @@
+from .. import bs2051
+import pytest
+
+
+@pytest.fixture(params=bs2051.layout_names)
+def layout_with_lfe(request):
+ """Layout object."""
+ return bs2051.get_layout(request.param)
+
+
+@pytest.fixture
+def layout(layout_with_lfe):
+ return layout_with_lfe.without_lfe
diff --git a/ear/core/test/data/psp_pvs/0+2+0.npz b/ear/core/test/data/psp_pvs/0+2+0.npz
new file mode 100644
index 00000000..186fd4dc
Binary files /dev/null and b/ear/core/test/data/psp_pvs/0+2+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/0+5+0.npz b/ear/core/test/data/psp_pvs/0+5+0.npz
new file mode 100644
index 00000000..de804790
Binary files /dev/null and b/ear/core/test/data/psp_pvs/0+5+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/0+7+0.npz b/ear/core/test/data/psp_pvs/0+7+0.npz
new file mode 100644
index 00000000..9f64c1cc
Binary files /dev/null and b/ear/core/test/data/psp_pvs/0+7+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/2+5+0.npz b/ear/core/test/data/psp_pvs/2+5+0.npz
new file mode 100644
index 00000000..c766c68e
Binary files /dev/null and b/ear/core/test/data/psp_pvs/2+5+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/3+7+0.npz b/ear/core/test/data/psp_pvs/3+7+0.npz
new file mode 100644
index 00000000..41815381
Binary files /dev/null and b/ear/core/test/data/psp_pvs/3+7+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/4+5+0.npz b/ear/core/test/data/psp_pvs/4+5+0.npz
new file mode 100644
index 00000000..2e631123
Binary files /dev/null and b/ear/core/test/data/psp_pvs/4+5+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/4+5+1.npz b/ear/core/test/data/psp_pvs/4+5+1.npz
new file mode 100644
index 00000000..62d114f1
Binary files /dev/null and b/ear/core/test/data/psp_pvs/4+5+1.npz differ
diff --git a/ear/core/test/data/psp_pvs/4+7+0.npz b/ear/core/test/data/psp_pvs/4+7+0.npz
new file mode 100644
index 00000000..5e670fc2
Binary files /dev/null and b/ear/core/test/data/psp_pvs/4+7+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/4+9+0.npz b/ear/core/test/data/psp_pvs/4+9+0.npz
new file mode 100644
index 00000000..24cfdaf0
Binary files /dev/null and b/ear/core/test/data/psp_pvs/4+9+0.npz differ
diff --git a/ear/core/test/data/psp_pvs/9+10+3.npz b/ear/core/test/data/psp_pvs/9+10+3.npz
new file mode 100644
index 00000000..c7a2b830
Binary files /dev/null and b/ear/core/test/data/psp_pvs/9+10+3.npz differ
diff --git a/ear/core/test/test_block_aligner.py b/ear/core/test/test_block_aligner.py
new file mode 100644
index 00000000..c9234e51
--- /dev/null
+++ b/ear/core/test/test_block_aligner.py
@@ -0,0 +1,50 @@
+import pytest
+import numpy as np
+import numpy.testing as npt
+from ..block_aligner import BlockAligner
+
+
+@pytest.mark.parametrize("delays", [
+ [0],
+ [0, 1],
+ [0, 1, 10],
+ [0, 1, 13],
+])
+def test_BlockAligner(delays):
+ """Test BlockAligner with one input stream with the given delay for each block in delays"""
+ nch = 5
+ bs = 10
+ ns = 105
+
+ ba = BlockAligner(nch)
+
+ all_samples = np.random.rand(len(delays), ns, nch)
+ max_delay = max(delays)
+
+ def delayed_blocks(samples, delay):
+ """Delayed sample blocks taken from samples, simulating a process fed
+ from a fixed length file with some inherent delay, with a trailing
+ max_delay sample block.
+ """
+ samples_delay = np.concatenate((np.zeros((delay, nch)),
+ samples,
+ np.zeros((max_delay, nch)),
+ ))
+ for start in range(0, ns, bs):
+ end = min(start + bs, ns)
+ yield samples_delay[start:end]
+
+ yield samples_delay[end: end + max_delay]
+
+ # start time of the next block for each input
+ starts = [-delay for delay in delays]
+
+ out_blocks = []
+ for blocks in zip(*[delayed_blocks(samples, delay) for samples, delay in zip(all_samples, delays)]):
+ for i, block in enumerate(blocks):
+ ba.add(starts[i], block)
+ starts[i] += len(block)
+ out_blocks.append(ba.get())
+
+ out_samples = np.concatenate(out_blocks)
+ npt.assert_allclose(out_samples, np.sum(all_samples, axis=0))
diff --git a/ear/core/test/test_bs2051.py b/ear/core/test/test_bs2051.py
new file mode 100644
index 00000000..452bd1d6
--- /dev/null
+++ b/ear/core/test/test_bs2051.py
@@ -0,0 +1,75 @@
+import pytest
+from .. import bs2051
+from ..geom import PolarPosition, relative_angle
+
+
+def test_get_layout_data():
+ layout = bs2051.get_layout("4+5+0")
+
+ assert layout.channel_names[:2] == ["M+030", "M-030"]
+
+ assert len(layout.channels) == 10
+
+ assert layout.channels[0].polar_position == PolarPosition(30, 0, 1)
+ assert layout.channels[1].polar_position == PolarPosition(-30, 0, 1)
+
+
+def test_layout_names():
+ assert "4+5+0" in bs2051.layout_names
+
+
+def test_unknown_layout():
+ with pytest.raises(KeyError) as excinfo:
+ data, positions = bs2051.get_layout("wat")
+
+ assert "wat" in str(excinfo.value)
+
+
+def test_all_positions_in_range():
+ """Check that the speaker positions of all layouts are within range."""
+ for layout in bs2051.layouts.values():
+ errors = []
+ layout.check_positions(callback=errors.append)
+ assert errors == []
+
+
+def test_azimuth_ranges():
+ """Test that most ranges are reasonably small; this detects ranges that
+ have been inverted. Screen speakers and LFE channels are ignored.
+ """
+ for layout in bs2051.layouts.values():
+ for channel in layout.channels:
+ if not channel.is_lfe and "SC" not in channel.name:
+ az_range = channel.az_range
+ range_size = relative_angle(az_range[0], az_range[1]) - az_range[0]
+ assert range_size <= 180
+
+
+def test_symmetry():
+ for layout in bs2051.layouts.values():
+ # find symmetric pairs of speakers
+ symmetric_pairs = {}
+ for channel in layout.channels:
+ for splitchar in "+-":
+ if splitchar in channel.name:
+ ident = tuple(channel.name.split(splitchar))
+ symmetric_pairs.setdefault(ident, []).append(channel)
+ break
+ else:
+ assert "LFE" in channel.name and channel.is_lfe
+
+ for pair in symmetric_pairs.values():
+ if len(pair) == 1:
+ # any non-paired speakers should be on the centre line
+ assert pair[0].polar_position.azimuth in (0, -180, 180)
+ elif len(pair) == 2:
+ # all pairs should be have symmetrical positions and ranges
+ a, b = pair
+
+ assert a.polar_position.elevation == b.polar_position.elevation
+ assert a.polar_position.azimuth == -b.polar_position.azimuth
+
+ assert a.el_range == b.el_range
+ assert a.az_range == (-b.az_range[1], -b.az_range[0])
+ else:
+ assert False
diff --git a/ear/core/test/test_convolver.py b/ear/core/test/test_convolver.py
new file mode 100644
index 00000000..2f062c0c
--- /dev/null
+++ b/ear/core/test/test_convolver.py
@@ -0,0 +1,54 @@
+import numpy as np
+import numpy.testing as npt
+import pytest
+from ..convolver import OverlapSaveConvolver, VariableBlockSizeAdapter
+
+
+@pytest.mark.parametrize("nchannels", [1, 3])
+def test_convolve(nchannels):
+ bs = 512
+ nchannels = 3
+ f = np.random.rand(1500, nchannels)
+ in_blocks = [np.random.rand(bs, nchannels) for i in range(100)]
+
+ c = OverlapSaveConvolver(bs, nchannels, f)
+ out_blocks = [c.filter_block(block) for block in in_blocks]
+
+ in_all = np.concatenate(in_blocks)
+ out_all = np.concatenate(out_blocks)
+
+ out_all_expected = np.stack((np.convolve(in_chan, f_chan, mode="full")[:len(out_all)]
+ for in_chan, f_chan in zip(in_all.T, f.T)),
+ axis=1)
+
+ npt.assert_allclose(out_all_expected, out_all)
+
+
+def test_variable_block_size():
+ block_size = 100
+ nchannels = 3
+
+ def process(samples):
+ assert samples.shape == (block_size, nchannels)
+ return samples.copy()
+
+ adapter = VariableBlockSizeAdapter(block_size, nchannels, process)
+
+ assert adapter.delay(5) == block_size + 5
+
+ input_blocks = []
+ output_blocks = []
+ for input_size in list(range(300)) + list(range(10)):
+ input_block = np.random.rand(input_size, nchannels)
+ output_block = adapter.process(input_block)
+ assert output_block.shape == input_block.shape
+
+ input_blocks.append(input_block)
+ output_blocks.append(output_block)
+
+ all_input = np.concatenate(input_blocks)
+ all_output = np.concatenate(output_blocks)
+
+ expected_output = np.concatenate((np.zeros((block_size, nchannels)), all_input[:len(all_input) - block_size]))
+
+ npt.assert_allclose(expected_output, all_output)
diff --git a/ear/core/test/test_delay.py b/ear/core/test/test_delay.py
new file mode 100644
index 00000000..1df16a79
--- /dev/null
+++ b/ear/core/test/test_delay.py
@@ -0,0 +1,26 @@
+import numpy as np
+import numpy.testing as npt
+import pytest
+from ..delay import Delay
+
+
+@pytest.mark.parametrize("delay", [0, 256, 511, 512, 513, 1024])
+def test_delay(delay):
+ nchannels = 4
+ block_size = 512
+ nsamples = block_size * 10
+
+ input_samples = np.random.random((nsamples, nchannels))
+
+ d = Delay(nchannels, delay)
+
+ out_blocks = []
+ for start in range(0, nsamples, block_size):
+ out = d.process(input_samples[start:start+block_size])
+ out_blocks.append(out)
+
+ out = np.concatenate(out_blocks)
+
+ expected_out = np.concatenate((np.zeros((delay, nchannels)), input_samples[:len(input_samples)-delay]))
+
+ npt.assert_allclose(expected_out, out)
diff --git a/ear/core/test/test_geom.py b/ear/core/test/test_geom.py
new file mode 100644
index 00000000..ea81f478
--- /dev/null
+++ b/ear/core/test/test_geom.py
@@ -0,0 +1,126 @@
+from ..geom import azimuth, elevation, cart, relative_angle, inside_angle_range, ngon_vertex_order, local_coordinate_system
+import numpy as np
+import numpy.testing as npt
+
+
+def test_azimuth():
+ npt.assert_allclose(azimuth(np.array([1.0, 1.0, 0.0])), -45)
+
+
+def test_elevation():
+ npt.assert_allclose(elevation(np.array([0.0, 1.0, 1.0])), 45)
+ npt.assert_allclose(elevation(np.array([np.sqrt(2), np.sqrt(2), 2.0])), 45)
+
+
+def test_relative_angle():
+ assert relative_angle(0, 10) == 10
+ assert relative_angle(10, 10) == 10
+ assert relative_angle(11, 10) == 370
+ assert relative_angle(370, 10) == 370
+ assert relative_angle(371, 10) == 360 + 370
+
+
+def test_inside_angle_range():
+ assert inside_angle_range(0, 0, 10)
+ assert inside_angle_range(5, 0, 10)
+ assert inside_angle_range(10, 0, 10)
+
+ assert inside_angle_range(0, 10, 00)
+ assert not inside_angle_range(5, 10, 0)
+ assert inside_angle_range(15, 10, 0)
+ assert inside_angle_range(10, 10, 0)
+
+ assert inside_angle_range(0, -10, 10)
+ assert not inside_angle_range(180, -10, 10)
+ assert not inside_angle_range(-180, -10, 10)
+
+ assert inside_angle_range(0, -180, 180)
+ assert not inside_angle_range(0, -181, 181)
+ assert inside_angle_range(0, -180, 180, 1)
+
+ assert inside_angle_range(180, 180, -180)
+ assert inside_angle_range(180, 180, -180, 1)
+ assert not inside_angle_range(90, 180, -180)
+
+ assert inside_angle_range(0, 0, 0)
+ assert inside_angle_range(0, 0, 0, 1)
+ assert not inside_angle_range(90, 0, 0)
+
+ assert inside_angle_range(0, 1, 2, 2)
+ assert inside_angle_range(-1, 1, 2, 2)
+ assert inside_angle_range(359, 1, 2, 2)
+
+
+def test_order_vertices():
+ import itertools
+
+ def random_linear_transforms(ngon):
+ yield ngon
+
+ for i in range(10):
+ T = np.random.normal(size=(3, 3))
+ offset = np.random.normal(size=3)
+
+ # ... providing that the transform isn't rank deficient
+ if np.linalg.matrix_rank(T, tol=1e-3) < 3:
+ continue
+
+ yield ngon.dot(T) + offset
+
+ def no_random(ngon):
+ yield ngon
+
+ ordered_ngons = [
+ (random_linear_transforms, np.array([cart(30, 0, 1), cart(-30, 0, 1),
+ cart(-30, 30, 1), cart(30, 30, 1)])),
+ (no_random, np.array([cart(30, 0, 1), cart(0, 0, 1), cart(-30, 0, 1),
+ cart(-30, 30, 1), cart(30, 30, 1)])),
+ (random_linear_transforms, np.array([cart(30, 30, 1), cart(-30, 30, 1),
+ cart(-110, 30, 1), cart(110, 30, 1)])),
+ (random_linear_transforms, np.array([cart(30, 30, 1), cart(0, 30, 1), cart(-30, 30, 1),
+ cart(-110, 30, 1), cart(110, 30, 1)])),
+ (random_linear_transforms, np.array([cart(30, 0, 1), cart(0, 0, 1), cart(-30, 0, 1),
+ cart(-110, 0, 1), cart(110, 0, 1)])),
+ ]
+
+ def in_same_order(a, b):
+ """Are a and b the same, module some reversal or shift?"""
+ # just produce all shifted and reversed versions of a, and compare
+ # against b. Inefficient but simple.
+ for offset in range(len(a)):
+ a_shift = np.concatenate((a[offset:], a[:offset]))
+ for reversal in (1, -1):
+ a_reorder = a_shift[::reversal]
+ if np.all(a_reorder == b):
+ return True
+ return False
+
+ for randomize, ordered_ngon in ordered_ngons:
+ for ordered_ngon_T in randomize(ordered_ngon):
+ # for each permutation, check that reordering results in the same
+ # ordering out
+ for unordered_ngon in itertools.permutations(ordered_ngon_T):
+ order = ngon_vertex_order(unordered_ngon)
+ reordered_ngon = np.array(unordered_ngon)[order]
+
+ assert in_same_order(reordered_ngon, ordered_ngon_T)
+
+
+def test_local_coordinate_system():
+ x, y, z = np.eye(3)
+ npt.assert_allclose(local_coordinate_system(0, 0),
+ [x, y, z], atol=1e-15)
+ npt.assert_allclose(local_coordinate_system(-90, 0),
+ [-y, x, z], atol=1e-15)
+ npt.assert_allclose(local_coordinate_system(90, 0),
+ [y, -x, z], atol=1e-15)
+ npt.assert_allclose(local_coordinate_system(180, 0),
+ [-x, -y, z], atol=1e-15)
+
+ npt.assert_allclose(local_coordinate_system(0, 90),
+ [x, z, -y], atol=1e-15)
+ npt.assert_allclose(local_coordinate_system(0, -90),
+ [x, -z, y], atol=1e-15)
+
+ npt.assert_allclose(local_coordinate_system(-90, 90),
+ [-y, z, -x], atol=1e-15)
diff --git a/ear/core/test/test_hoa.py b/ear/core/test/test_hoa.py
new file mode 100644
index 00000000..558652a9
--- /dev/null
+++ b/ear/core/test/test_hoa.py
@@ -0,0 +1,142 @@
+import numpy as np
+import numpy.testing as npt
+from .. import hoa
+
+
+def test_acn():
+ acn = 0
+ for n in range(10):
+ for m in range(-n, n+1):
+ assert hoa.to_acn(n, m) == acn
+ assert hoa.from_acn(acn) == (n, m)
+ acn += 1
+
+
+def test_Alegendre():
+ # associsted Legendre polynomials for specific n, m, including the
+ # condon-shortley phase.
+ # from http://mathworld.wolfram.com/AssociatedLegendrePolynomial.html
+ testcases = {
+ (0, 0): lambda x: 1.0,
+ (1, 0): lambda x: x,
+ (1, 1): lambda x: -np.sqrt(1-x**2.0),
+ (2, 0): lambda x: 0.5 * (3.0 * x**2.0 - 1.0),
+ (2, 1): lambda x: -3.0 * x * np.sqrt(1.0 - x**2.0),
+ (2, 2): lambda x: 3.0 * (1.0 - x**2),
+ }
+
+ @np.vectorize
+ def test_Alegendre(n, m, x):
+ # test version of hoa.Alegendre without the condon-shortley phase
+ return (-1.0)**m * testcases[(n, m)](x)
+
+ x = np.linspace(0, 1, 10)
+ n, m = np.array(list(testcases)).T
+
+ N, M, X = np.broadcast_arrays(n, m, x[:, np.newaxis])
+
+ expected_res = test_Alegendre(N, M, X)
+ res = hoa.Alegendre(N, M, X)
+ npt.assert_allclose(res, expected_res)
+
+ # test cases from "Ambix-a suggested ambisonics format."
+ npt.assert_allclose(hoa.Alegendre(np.arange(4), np.arange(4), 0.0),
+ [1, 1, 3, 15])
+
+
+def test_sph_harm_first_order(): # noqa
+ # check that first order gives the same results as b-format panning
+
+ # conversion matrix from "Ambix-a suggested ambisonics format.", omitting
+ # the W scale both here and in testcases.
+ b_to_acn = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [0, 1, 0, 0]])
+ acn_to_b = b_to_acn.T
+
+ testcases = [
+ # ((az, el), [W, X, Y, Z]), # noqa: E122
+ ((0, 0), [1, 1, 0, 0]), # noqa: E241
+ ((90, 0), [1, 0, 1, 0]), # noqa: E241
+ ((-90, 0), [1, 0, -1, 0]), # noqa: E241
+ ((180, 0), [1, -1, 0, 0]), # noqa: E241
+ ((0, 90), [1, 0, 0, 1]), # noqa: E241
+ ((0, -90), [1, 0, 0, -1]), # noqa: E241
+ ]
+
+ azimuths = np.array([az for (az, el), xs in testcases]) # noqa: E221
+ elevations = np.array([el for (az, el), xs in testcases]) # noqa: E221
+ expected_b = np.array([xs for (az, el), xs in testcases]) # noqa: E221
+
+ n, m = hoa.from_acn(np.arange(4))
+
+ res_acn = hoa.sph_harm(n[np.newaxis], m[np.newaxis],
+ np.radians(azimuths)[:, np.newaxis],
+ np.radians(elevations)[:, np.newaxis])
+ res_b = np.dot(acn_to_b, res_acn.T).T
+
+ npt.assert_allclose(res_b, expected_b, atol=1e-10)
+
+
+def test_allrad_design():
+ from .. import bs2051
+ from .. import point_source
+
+ layout = bs2051.get_layout("4+5+0").without_lfe
+ psp = point_source.configure(layout)
+
+ order = 3
+ n, m = hoa.from_acn(range((order + 1)**2))
+
+ points = hoa.load_points()
+ decoder = hoa.allrad_design(points, psp.handle, n, m)
+
+ assert decoder.shape == (psp.num_channels, len(n))
+
+ # test that for each channel, encoding an HOA signal located at the
+ # position of this channel results in that channel having the largest gain
+ # after decoding; this is not guaranteed in general, but should be true for
+ # high orders and small layouts
+ for i, channel in enumerate(layout.channels):
+ # hoa coord system is in radians
+ az, el = np.radians([channel.polar_position.azimuth,
+ channel.polar_position.elevation])
+
+ encoded = hoa.sph_harm(n, m, az, el)
+ decoded = np.dot(decoder, encoded)
+ assert np.argmax(decoded) == i
+
+
+def test_maxRE():
+ """Check that the approximate and numeric versions of maxRE are compatible."""
+ for order in range(1, 5):
+ npt.assert_allclose(
+ hoa.ApproxMaxRECoefficients(order),
+ hoa.MaxRECoefficients(order),
+ rtol=1e-2)
+
+
+def plot_sph_harm(n, m):
+ """Plot the spherical harmonics with order n and degree m."""
+ az, el = np.ogrid[-180:181:5, -90:91:5]
+ value = hoa.sph_harm(n, m, np.radians(az), np.radians(el))
+
+ from ..geom import cart
+ x, y, z = cart(az, el, np.abs(value))
+
+ import matplotlib.pyplot as plt
+ from mpl_toolkits.mplot3d import Axes3D # noqa
+
+ fig = plt.figure()
+ plt.title("Spherical harmonic order {n} degree {m}".format(n=n, m=m))
+ plt.axis('off')
+ ax = fig.add_subplot(111, projection='3d', aspect=1)
+
+ colors = np.empty(value.shape, dtype=str)
+ colors[value > 0] = "r"
+ colors[value <= 0] = "b"
+
+ ax.plot_surface(x, y, z, facecolors=colors)
+
+ ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1)
+ ax.set_xlabel("x"); ax.set_ylabel("y"); ax.set_zlabel("z")
+
+ plt.show()
diff --git a/ear/core/test/test_importance.py b/ear/core/test/test_importance.py
new file mode 100644
index 00000000..4efd910f
--- /dev/null
+++ b/ear/core/test/test_importance.py
@@ -0,0 +1,113 @@
+from ..metadata_input import ObjectRenderingItem, ImportanceData, MetadataSourceIter, MetadataSource
+from ..metadata_input import ObjectTypeMetadata
+from ...fileio.adm.elements import AudioBlockFormatObjects
+from ..importance import filter_by_importance, filter_audioObject_by_importance, filter_audioPackFormat_by_importance, MetadataSourceImportanceFilter
+from fractions import Fraction
+import pytest
+
+
+@pytest.fixture
+def rendering_items():
+ dummySource = MetadataSource()
+ return [ObjectRenderingItem(importance=ImportanceData(audio_object=1, audio_pack_format=2), track_index=1, metadata_source=dummySource),
+ ObjectRenderingItem(importance=ImportanceData(audio_object=2, audio_pack_format=3), track_index=1, metadata_source=dummySource),
+ ObjectRenderingItem(importance=ImportanceData(audio_object=2, audio_pack_format=None), track_index=1, metadata_source=dummySource),
+ ObjectRenderingItem(importance=ImportanceData(audio_object=3, audio_pack_format=10), track_index=1, metadata_source=dummySource),
+ ObjectRenderingItem(importance=ImportanceData(audio_object=None, audio_pack_format=2), track_index=1, metadata_source=dummySource),
+ ObjectRenderingItem(importance=ImportanceData(audio_object=4, audio_pack_format=7), track_index=1, metadata_source=dummySource),
+ ObjectRenderingItem(importance=ImportanceData(audio_object=10, audio_pack_format=3), track_index=1, metadata_source=dummySource)]
+
+
+@pytest.mark.parametrize('threshold,expected_indizes', [
+ (0, [0, 1, 2, 3, 4, 5, 6]),
+ (1, [0, 1, 2, 3, 4, 5, 6]),
+ (2, [1, 2, 3, 4, 5, 6]),
+ (3, [3, 4, 5, 6]),
+ (4, [4, 5, 6]),
+ (5, [4, 6]),
+ (6, [4, 6]),
+ (7, [4, 6]),
+ (8, [4, 6]),
+ (9, [4, 6]),
+ (10, [4, 6])
+])
+def test_importance_filter_objects(rendering_items, threshold, expected_indizes):
+ expected_result = [rendering_items[x] for x in expected_indizes]
+ items = list(filter_audioObject_by_importance(rendering_items, threshold=threshold))
+ assert items == expected_result
+
+
+@pytest.mark.parametrize('threshold,expected_indizes', [
+ (0, [0, 1, 2, 3, 4, 5, 6]),
+ (1, [0, 1, 2, 3, 4, 5, 6]),
+ (2, [0, 1, 2, 3, 4, 5, 6]),
+ (3, [1, 2, 3, 5, 6]),
+ (4, [2, 3, 5]),
+ (5, [2, 3, 5]),
+ (6, [2, 3, 5]),
+ (7, [2, 3, 5]),
+ (8, [2, 3]),
+ (9, [2, 3]),
+ (10, [2, 3])
+])
+def test_importance_filter_packs(rendering_items, threshold, expected_indizes):
+ expected_result = [rendering_items[x] for x in expected_indizes]
+ items = list(filter_audioPackFormat_by_importance(rendering_items, threshold=threshold))
+ assert items == expected_result
+
+
+@pytest.mark.parametrize('threshold,expected_indizes', [
+ (0, [0, 1, 2, 3, 4, 5, 6]),
+ (1, [0, 1, 2, 3, 4, 5, 6]),
+ (2, [1, 2, 3, 4, 5, 6]),
+ (3, [3, 5, 6]),
+ (4, [5]),
+ (5, []),
+ (6, []),
+ (7, []),
+ (8, []),
+ (9, []),
+ (10, [])
+])
+def test_importance_filter(rendering_items, threshold, expected_indizes):
+ expected_result = [rendering_items[x] for x in expected_indizes]
+ items = list(filter_by_importance(rendering_items, threshold=threshold))
+ assert items == expected_result
+
+
+type_metadatas = [
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(0), position={'azimuth': 0, 'elevation': 0})),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(1), position={'azimuth': 0, 'elevation': 0})),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(2), position={'azimuth': 0, 'elevation': 0}, importance=4)),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(3), position={'azimuth': 0, 'elevation': 0})),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(4), position={'azimuth': 0, 'elevation': 0}, importance=4)),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(5), position={'azimuth': 0, 'elevation': 0}, importance=3)),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(6), position={'azimuth': 0, 'elevation': 0})),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(7), position={'azimuth': 0, 'elevation': 0}, importance=8)),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(8), position={'azimuth': 0, 'elevation': 0})),
+ ObjectTypeMetadata(block_format=AudioBlockFormatObjects(rtime=Fraction(9), position={'azimuth': 0, 'elevation': 0}))
+]
+
+
+@pytest.mark.parametrize('threshold,muted_indizes', [
+ (0, []),
+ (1, []),
+ (2, []),
+ (3, []),
+ (4, [5]),
+ (5, [2, 4, 5]),
+ (6, [2, 4, 5]),
+ (7, [2, 4, 5]),
+ (8, [2, 4, 5]),
+ (9, [2, 4, 5, 7]),
+ (10, [2, 4, 5, 7])
+])
+def test_importance_filter_source(threshold, muted_indizes):
+ source = MetadataSourceIter(type_metadatas)
+ adapted = MetadataSourceImportanceFilter(source, threshold=threshold)
+ for idx in range(len(type_metadatas)):
+ block = adapted.get_next_block()
+ if idx in muted_indizes:
+ assert block.block_format.gain == 0.0
+ else:
+ assert block == type_metadatas[idx]
diff --git a/ear/core/test/test_layout.py b/ear/core/test/test_layout.py
new file mode 100644
index 00000000..1a5b17a3
--- /dev/null
+++ b/ear/core/test/test_layout.py
@@ -0,0 +1,187 @@
+from ..layout import Channel, Layout, load_speakers, load_real_layout, Speaker, RealLayout
+from ..geom import cart, PolarPosition, CartesianPosition
+from ...common import PolarScreen, CartesianScreen
+from attr import evolve
+import pytest
+import numpy as np
+import numpy.testing as npt
+
+
+@pytest.fixture
+def layout():
+ # odd nominal positions, for testing
+ return Layout(name="test", channels=[
+ Channel(name="M+030", polar_position=(30, 0.0, 2.0),
+ polar_nominal_position=(25, 0.0, 1.5), az_range=(25, 30), el_range=(0, 0), is_lfe=False),
+ Channel(name="M-030", polar_position=PolarPosition(-30, 0.0, 2.0),
+ polar_nominal_position=PolarPosition(-25, 0.0, 1.5), az_range=(-30, -25)),
+ ])
+
+
+def test_positions(layout):
+ npt.assert_allclose(layout.positions, [cart(30, 0, 2), cart(-30, 0, 2)])
+
+
+def test_norm_positions(layout):
+ npt.assert_allclose(layout.norm_positions, [cart(30, 0, 1), cart(-30, 0, 1)])
+
+
+def test_nominal_positions(layout):
+ npt.assert_allclose(layout.nominal_positions, [cart(25, 0, 1.5), cart(-25, 0, 1.5)])
+
+
+def test_without_lfe(layout):
+ lfe_channel = Channel(name="LFE", polar_position=(30, -20, 2), is_lfe=True)
+ layout_lfe = evolve(layout, channels=layout.channels + [lfe_channel])
+ assert len(layout_lfe.channels) == 3
+ assert len(layout_lfe.without_lfe.channels) == 2
+
+
+def test_channel_names(layout):
+ assert layout.channel_names == ["M+030", "M-030"]
+
+
+def test_channels_by_name(layout):
+ assert layout.channels_by_name == {
+ "M+030": layout.channels[0],
+ "M-030": layout.channels[1],
+ }
+
+
+def test_default_nominal_range():
+ # defaulted nominal position and ranges should be kept when the position is modified
+ default_channel = Channel(name="name", polar_position=(10, 20, 1))
+ modified_channel = evolve(default_channel, polar_position=PolarPosition(30, 40, 1))
+ for channel in [default_channel, modified_channel]:
+ assert channel.polar_nominal_position == PolarPosition(10, 20, 1)
+ assert channel.az_range == (10, 10)
+ assert channel.el_range == (20, 20)
+
+
+def test_Channel_check_position():
+ errors = []
+ Channel(name="name", polar_position=(10, 20, 1)).check_position(callback=errors.append)
+ Channel(name="name", polar_position=(180, 20, 1), az_range=(175, -175)).check_position(callback=errors.append)
+ Channel(name="name", polar_position=(180, 20, 1), az_range=(180, 180)).check_position(callback=errors.append)
+ assert not errors
+
+ errors = []
+ Channel(name="name", polar_position=(10, 20, 1), az_range=(-5, 5)).check_position(callback=errors.append)
+ assert errors == ["name: azimuth 10.0 out of range (-5, 5)."]
+
+ errors = []
+ Channel(name="name", polar_position=(10, 20, 1), el_range=(0, 15)).check_position(callback=errors.append)
+ assert errors == ["name: elevation 20.0 out of range (0, 15)."]
+
+
+def test_Layout_check_position(layout):
+ errors = []
+ layout.check_positions(callback=errors.append)
+ assert errors == []
+
+ layout_err = evolve(layout, channels=[
+ (evolve(channel, polar_position=PolarPosition(30, 10, 1)) if channel.name == "M+030" else channel)
+ for channel in layout.channels])
+
+ errors = []
+ layout_err.check_positions(callback=errors.append)
+ assert errors == ["M+030: elevation 10.0 out of range (0, 0)."]
+
+
+def test_Layout_with_speakers_real_layout(layout):
+ speakers = [Speaker(channel=1, names=["M+030"], polar_position=PolarPosition(25, 0, 1.5)),
+ Speaker(channel=2, names=["M-030"]),
+ Speaker(channel=3, names=["M-110"])]
+ screen = PolarScreen(aspectRatio=1.5, centrePosition=PolarPosition(10.0, 20.0, 2.0), widthAzimuth=30.0)
+
+ new_layout, upmix = layout.with_speakers(speakers)
+ npt.assert_allclose(new_layout.positions, [cart(25, 0, 1.5), cart(-30, 0, 2)])
+ npt.assert_allclose(upmix, [[0, 0], [1, 0], [0, 1], [0, 0]])
+
+ new_layout, upmix = layout.with_real_layout(RealLayout(speakers=speakers, screen=screen))
+ npt.assert_allclose(new_layout.positions, [cart(25, 0, 1.5), cart(-30, 0, 2)])
+ npt.assert_allclose(upmix, [[0, 0], [1, 0], [0, 1], [0, 0]])
+ assert new_layout.screen == screen
+
+
+def test_Layout_check_upmix_matrix(layout):
+ errors = []
+ upmix = np.array([[0, 0],
+ [1, 0],
+ [0, 0.5],
+ [0, 0]])
+ layout.check_upmix_matrix(upmix, callback=errors.append)
+ assert errors == []
+
+ errors = []
+ upmix = np.array([[0, 0],
+ [1, 0],
+ [0, 0],
+ [0, 0]])
+ layout.check_upmix_matrix(upmix, callback=errors.append)
+ assert errors == ["Channel M-030 not mapped to any output."]
+
+ errors = []
+ upmix = np.array([[0, 0],
+ [1, 0],
+ [0, 1],
+ [0, 1]])
+ layout.check_upmix_matrix(upmix, callback=errors.append)
+ assert errors == ["Channel M-030 mapped to multiple outputs: [2, 3]."]
+
+ errors = []
+ upmix = np.array([[0, 0],
+ [1, 1],
+ [0, 0],
+ [0, 0]])
+ layout.check_upmix_matrix(upmix, callback=errors.append)
+ assert errors == ["Speaker idx 1 used by multiple channels: ['M+030', 'M-030']"]
+
+
+def test_load_layout_info():
+ def run_test(yaml_obj, expected, func=load_real_layout):
+ from ruamel import yaml
+ from six import StringIO
+ yaml_str = yaml.dump(yaml_obj)
+
+ result = func(StringIO(yaml_str))
+
+ assert expected == result
+
+ run_test(dict(speakers=[dict(channel=0, names="M+000")]),
+ RealLayout(speakers=[Speaker(0, ["M+000"])]))
+
+ run_test(dict(speakers=[dict(channel=0, names=["M+000"])]),
+ RealLayout(speakers=[Speaker(0, ["M+000"])]))
+
+ run_test(dict(speakers=[dict(channel=0, names=["M+000"], position=dict(az=10, el=5, r=1))]),
+ RealLayout(speakers=[Speaker(0, ["M+000"], PolarPosition(10, 5, 1))]))
+
+ run_test(dict(speakers=[dict(channel=0, names=["M+000"], gain_linear=0.5)]),
+ RealLayout(speakers=[Speaker(0, ["M+000"], gain_linear=0.5)]))
+
+ with pytest.raises(Exception) as excinfo:
+ run_test(dict(speakers=[dict(channel=0, names=["M+000"], position=dict(az=10, el=5))]),
+ RealLayout(speakers=[Speaker(0, ["M+000"], PolarPosition(10, 5, 1))]))
+ assert "Unknown position format" in str(excinfo.value)
+
+ # old style with speakers at the top level
+ run_test([dict(channel=0, names="M+000")],
+ RealLayout(speakers=[Speaker(0, ["M+000"])]))
+
+ # polar screen
+ run_test(dict(screen=dict(type="polar", aspectRatio=1.5, centrePosition=dict(az=10, el=20, r=2), widthAzimuth=30)),
+ RealLayout(screen=PolarScreen(aspectRatio=1.5, centrePosition=PolarPosition(10.0, 20.0, 2.0), widthAzimuth=30.0)))
+
+ # Cartesian screen
+ run_test(dict(screen=dict(type="cart", aspectRatio=1.5, centrePosition=dict(X=0.1, Y=0.9, Z=0.2), widthX=0.3)),
+ RealLayout(screen=CartesianScreen(aspectRatio=1.5, centrePosition=CartesianPosition(0.1, 0.9, 0.2), widthX=0.3)))
+
+ # passes through null screens
+ run_test(dict(screen=None),
+ RealLayout(screen=None))
+
+ # legacy speakers wrapper
+ run_test(dict(speakers=[dict(channel=0, names="M+000")]),
+ [Speaker(0, ["M+000"])],
+ func=load_speakers)
diff --git a/ear/core/test/test_monitor.py b/ear/core/test/test_monitor.py
new file mode 100644
index 00000000..c5b1d4e6
--- /dev/null
+++ b/ear/core/test/test_monitor.py
@@ -0,0 +1,29 @@
+import numpy as np
+from ..monitor import PeakMonitor
+
+
+def test_peak_monitor():
+ mon = PeakMonitor(2)
+
+ samples = np.zeros((1000, 2))
+ mon.process(samples)
+ assert not mon.has_overloaded()
+
+ samples = np.zeros((1000, 2))
+ samples[100, 0] = 0.9
+ mon.process(samples)
+ assert not mon.has_overloaded()
+
+ samples = np.zeros((1000, 2))
+ samples[100, 1] = 10.0
+ mon.process(samples)
+ assert mon.has_overloaded()
+
+ samples = np.zeros((1000, 2))
+ mon.process(samples)
+ assert mon.has_overloaded()
+
+ import pytest
+ with pytest.warns(None) as record:
+ mon.warn_overloaded()
+ assert len(record) == 1 and str(record[0].message) == "overload in channel 1; peak level was 20.0dBFS"
diff --git a/ear/core/test/test_point_source.py b/ear/core/test/test_point_source.py
new file mode 100644
index 00000000..d09275ca
--- /dev/null
+++ b/ear/core/test/test_point_source.py
@@ -0,0 +1,145 @@
+import numpy as np
+import numpy.testing as npt
+from ..point_source import Triplet, VirtualNgon, StereoPanDownmix, PointSourcePanner, configure
+from ..geom import cart, azimuth
+import pytest
+
+
+def test_virtual():
+ positions = np.array([cart(30, 0, 1), cart(-30, 0, 1),
+ cart(30, 30, 1), cart(-30, 30, 1)])
+ virtual_downmix = np.array([0.2, 0.2, 0.3, 0.3])
+ virtual_pos = virtual_downmix.dot(positions)
+
+ ng = VirtualNgon(range(4), positions, virtual_pos, virtual_downmix)
+
+ # if we pan to the virtual speaker, the output should be the normalised downmix
+ npt.assert_allclose(ng.handle(virtual_pos), virtual_downmix / np.linalg.norm(virtual_downmix))
+
+ # for random linear combinations of the loudspeakers positions...
+ proportions = np.random.random((100, 4))
+ for proportion in proportions:
+ # ... we can calculate a position within the ngon...
+ pos = np.dot(positions.T, proportion)
+ pos /= np.linalg.norm(pos)
+
+ # ... which when rendered...
+ pv = ng.handle(pos)
+
+ # ... can be multiplied by the speaker positions to produce another position...
+ pos_calc = pv.dot(positions)
+ pos_calc /= np.linalg.norm(pos_calc)
+
+ # ... that should be the same as the one we started with
+ npt.assert_allclose(pos, pos_calc)
+
+
+def test_stereo_downmix():
+ p = StereoPanDownmix(0, 1)
+
+ pos_gains = [
+ (cart(0, 0, 1), np.sqrt([0.5, 0.5])),
+ (cart(-30, 0, 1), np.sqrt([0.0, 1.0])),
+ (cart(-110, 0, 1), np.sqrt([0.0, 0.5])),
+ (cart(-180, 0, 1), np.sqrt([0.25, 0.25])),
+ ]
+
+ for pos, gains in pos_gains:
+ npt.assert_allclose(p.handle(pos), gains, atol=1e-5)
+
+ spk_pos = [cart(30, 0, 1), cart(-30, 0, 1)]
+ pv = p.handle(cart(15, 0, 1))
+ npt.assert_allclose(azimuth(np.dot(pv, spk_pos)), 15)
+
+
+def test_PointSourcePanner():
+ positions = np.array([
+ cart(30, 0, 1), cart(0, 0, 1), cart(-30, 0, 1),
+ cart(0, 30, 1)
+ ])
+
+ tris = [[0, 1, 3], [2, 1, 3]]
+ regions = [Triplet(tri, positions[tri]) for tri in tris]
+
+ # should fail if not given enough channels
+ with pytest.raises(AssertionError):
+ PointSourcePanner(regions, len(positions) - 1)
+
+ psp = PointSourcePanner(regions)
+ assert psp.num_channels == len(positions)
+
+ for i, position in enumerate(positions):
+ pv_req = np.zeros(len(positions))
+ pv_req[i] = 1.0
+
+ npt.assert_allclose(psp.handle(position), pv_req)
+
+ assert psp.handle(np.array([0, -1, 0])) is None
+
+
+def test_all_layouts(layout):
+ config = configure(layout)
+
+ # calculate gains for every position on a grid
+ azimuths, elevations = np.meshgrid(np.linspace(-180, 180, 61),
+ np.linspace(-90, 90, 31))
+
+ positions = cart(azimuths, elevations, 1)
+
+ pv = np.apply_along_axis(config.handle, 2, positions)
+
+ assert np.all(pv >= 0)
+
+ # check that symmetric positions produce symmetric panning values
+
+ spk_positions = layout.norm_positions
+ # channel map that exchanges speakers which have inverse x positions; this
+ # assumes that the layout is left-right symmetric
+ flip_remap = [
+ np.argmin(np.linalg.norm(spk_positions - [-pos[0], pos[1], pos[2]], axis=1))
+ for pos in spk_positions]
+
+ npt.assert_allclose(pv, pv[:, ::-1, flip_remap], atol=1e-10)
+
+ # check that the gains are normalised
+
+ # stereo is normalised only at the front, and at the back at -3dB
+ if layout.name == "0+2+0":
+ gains_normalised = (elevations == 0) & ((np.abs(azimuths) <= 30) | (np.abs(azimuths) >= 110))
+
+ gains_target = np.zeros_like(azimuths)
+ gains_target[(np.abs(azimuths) <= 30) & (elevations == 0)] = 1.0
+ gains_target[(np.abs(azimuths) >= 110) & (elevations == 0)] = np.sqrt(0.5)
+
+ npt.assert_allclose(np.linalg.norm(pv[gains_normalised], axis=1),
+ gains_target[gains_normalised])
+ assert np.all(np.linalg.norm(pv, axis=2) >= np.sqrt(0.5) - 1e-6)
+ else:
+
+ npt.assert_allclose(np.linalg.norm(pv, axis=2), 1)
+
+ # check that the velocity vector matches the source position
+
+ # true if the panner should pass the tests at the corresponding position
+ vv_at_pos = np.ones_like(azimuths, dtype=bool)
+
+ if layout.name == "0+2+0":
+ vv_at_pos = (np.abs(azimuths) <= 30) & (elevations == 0)
+ elif layout.name in ["0+5+0", "2+5+0", "0+7+0"]:
+ vv_at_pos = elevations == 0
+ elif layout.name.endswith("+0"):
+ vv_at_pos = elevations >= 0
+ else:
+ vv_at_pos = np.ones_like(azimuths, dtype=bool)
+
+ # all layouts have remapping below the horizontal plane
+ vv_at_pos &= elevations >= 0
+ # only 9+10+3 has no remapping above the horizontal plane
+ if "U+180" not in layout.channel_names and "UH+180" not in layout.channel_names:
+ vv_at_pos &= elevations <= 0
+
+ assert np.count_nonzero(vv_at_pos) > 0
+
+ vv = np.dot(pv, layout.positions)
+ vv /= np.linalg.norm(vv, axis=2, keepdims=True)
+ npt.assert_allclose(vv[vv_at_pos], positions[vv_at_pos], atol=1e-10)
diff --git a/ear/core/test/test_point_source_changes.py b/ear/core/test/test_point_source_changes.py
new file mode 100644
index 00000000..fa08262f
--- /dev/null
+++ b/ear/core/test/test_point_source_changes.py
@@ -0,0 +1,29 @@
+import py.path
+import numpy as np
+import numpy.testing as npt
+import pytest
+from ..geom import cart
+from ..point_source import configure
+
+files_dir = py.path.local(__file__).dirpath() / "data" / "psp_pvs"
+
+
+@pytest.mark.no_cover
+def test_no_change_in_pvs(layout):
+ """check that the results of the point source panner haven't changed"""
+ config = configure(layout)
+
+ azimuths, elevations = np.meshgrid(np.linspace(-180, 180, 31),
+ np.linspace(-90, 90, 15))
+ positions = cart(azimuths, elevations, 1)
+ pv = np.apply_along_axis(config.handle, 2, positions)
+
+ data_path = files_dir / (layout.name + ".npz")
+
+ if data_path.check():
+ loaded_pv = np.load(str(data_path))["pv"]
+ npt.assert_allclose(pv, loaded_pv, atol=1e-6)
+ else:
+ data_path.dirpath().ensure_dir()
+ np.savez_compressed(str(data_path), pv=pv)
+ pytest.skip("generated pv file for layout {layout.name}".format(layout=layout))
diff --git a/ear/core/test/test_renderer_common.py b/ear/core/test/test_renderer_common.py
new file mode 100644
index 00000000..6147c2c7
--- /dev/null
+++ b/ear/core/test/test_renderer_common.py
@@ -0,0 +1,46 @@
+from fractions import Fraction
+import numpy as np
+import numpy.testing as npt
+from ..renderer_common import FixedGains, InterpGains
+
+
+def test_FixedGains():
+ g = FixedGains(
+ start_sample=Fraction(0.5),
+ end_sample=Fraction(10.5),
+ gains=np.array([0.5]),
+ )
+
+ sample_no = np.arange(11)
+ expected_gains = np.ones((11, 1)) * 0.5
+ expected_gains[(sample_no <= 0) | (sample_no > 10), :] = 0.0
+
+ input_samples = np.random.normal(size=11)
+ output_samples = np.random.normal(size=(11, 1))
+ expected = output_samples + input_samples[:, np.newaxis] * expected_gains
+
+ g.process(0, input_samples, output_samples)
+
+ npt.assert_allclose(output_samples, expected)
+
+
+def test_InterpGains():
+ g = InterpGains(
+ start_sample=Fraction(0.5),
+ end_sample=Fraction(10.5),
+ gains_start=np.array([1, 0]),
+ gains_end=np.array([0, 1]),
+ )
+
+ sample_no = np.arange(11)
+ p = np.interp(sample_no, (0.5, 10.5), (0, 1))
+ expected_gains = np.stack((1-p, p), 1)
+ expected_gains[(sample_no <= 0) | (sample_no > 10), :] = 0.0
+
+ input_samples = np.random.normal(size=11)
+ output_samples = np.random.normal(size=(11, 2))
+ expected = output_samples + input_samples[:, np.newaxis] * expected_gains
+
+ g.process(0, input_samples, output_samples)
+
+ npt.assert_allclose(output_samples, expected)
diff --git a/ear/core/test/test_screen_common.py b/ear/core/test/test_screen_common.py
new file mode 100644
index 00000000..5d262b39
--- /dev/null
+++ b/ear/core/test/test_screen_common.py
@@ -0,0 +1,79 @@
+import numpy as np
+from ..screen_common import PolarEdges, PolarScreen, CartesianEdges, CartesianScreen
+from ...common import default_screen, PolarPosition, CartesianPosition, cart, azimuth
+
+default_edge_elevation = np.degrees(np.arctan(np.tan(np.radians(58.0/2.0)) / 1.78))
+
+
+def test_polar_edges_from_polar():
+ screen_edges = PolarEdges.from_screen(default_screen)
+ assert np.isclose(screen_edges.left_azimuth, 29.0)
+ assert np.isclose(screen_edges.right_azimuth, -29.0)
+ assert np.isclose(screen_edges.top_elevation, default_edge_elevation)
+ assert np.isclose(screen_edges.bottom_elevation, -default_edge_elevation)
+
+
+def test_polar_edges_from_polar_left():
+ shifted_center_screen = PolarScreen(
+ aspectRatio=1.78,
+ centrePosition=PolarPosition(
+ azimuth=20.0,
+ elevation=0.0,
+ distance=1.0),
+ widthAzimuth=58.0)
+ screen_edges = PolarEdges.from_screen(shifted_center_screen)
+ assert np.isclose(screen_edges.left_azimuth, 49.0)
+ assert np.isclose(screen_edges.right_azimuth, -9.0)
+ assert np.isclose(screen_edges.top_elevation, default_edge_elevation)
+ assert np.isclose(screen_edges.bottom_elevation, -default_edge_elevation)
+
+
+def test_polar_edges_from_polar_up():
+ shifted_center_screen = PolarScreen(
+ aspectRatio=1.78,
+ centrePosition=PolarPosition(
+ azimuth=0.0,
+ elevation=10.0,
+ distance=1.0),
+ widthAzimuth=58.0)
+ screen_edges = PolarEdges.from_screen(shifted_center_screen)
+ assert np.isclose(screen_edges.left_azimuth, azimuth(cart(0, 10, 1) - [np.tan(np.radians(58/2)), 0, 0]))
+ assert np.isclose(screen_edges.right_azimuth, azimuth(cart(0, 10, 1) + [np.tan(np.radians(58/2)), 0, 0]))
+ assert np.isclose(screen_edges.top_elevation, default_edge_elevation + 10)
+ assert np.isclose(screen_edges.bottom_elevation, -default_edge_elevation + 10)
+
+
+def test_polar_edges_from_cart_default():
+ cartesian_default_screen = CartesianScreen(
+ aspectRatio=1.78,
+ centrePosition=CartesianPosition(
+ X=0.0,
+ Y=1.0,
+ Z=0.0),
+ widthX=2 * np.tan(np.radians(58.0/2.0)))
+ screen_edges = PolarEdges.from_screen(cartesian_default_screen)
+ assert np.isclose(screen_edges.left_azimuth, 29.0)
+ assert np.isclose(screen_edges.right_azimuth, -29.0)
+ assert np.isclose(screen_edges.top_elevation, default_edge_elevation)
+ assert np.isclose(screen_edges.bottom_elevation, -default_edge_elevation)
+
+
+def test_cartesian_edges():
+ screen_edges = CartesianEdges.from_screen(default_screen)
+ assert np.isclose(screen_edges.left_X, -0.554309051452769)
+ assert np.isclose(screen_edges.right_X, 0.554309051452769)
+ assert np.isclose(screen_edges.top_Z, 0.31140957946784775)
+ assert np.isclose(screen_edges.bottom_Z, -0.31140957946784775)
+
+ cartesian_default_screen = CartesianScreen(
+ aspectRatio=1.78,
+ centrePosition=CartesianPosition(
+ X=0.0,
+ Y=1.0,
+ Z=0.0),
+ widthX=1.108618102905538)
+ screen_edges = CartesianEdges.from_screen(cartesian_default_screen)
+ assert np.isclose(screen_edges.left_X, -0.554309051452769)
+ assert np.isclose(screen_edges.right_X, 0.554309051452769)
+ assert np.isclose(screen_edges.top_Z, 0.31140957946784775)
+ assert np.isclose(screen_edges.bottom_Z, -0.31140957946784775)
diff --git a/ear/core/test/test_subwoofer.py b/ear/core/test/test_subwoofer.py
new file mode 100644
index 00000000..152e27df
--- /dev/null
+++ b/ear/core/test/test_subwoofer.py
@@ -0,0 +1,49 @@
+import numpy as np
+import numpy.testing as npt
+from .. import bs2051
+from ..geom import cart
+from ..subwoofer import render_subwoofers, subwoofer_upmix_matrix, lfe_downmix_matrix
+
+
+def test_render_twosubs():
+ positions = np.array([
+ cart(30, -30, 1),
+ cart(-30, -30, 1),
+ ])
+ npt.assert_allclose(render_subwoofers(positions, cart(0, 0, 1)),
+ [0.5, 0.5])
+ npt.assert_allclose(render_subwoofers(positions, cart(30, 0, 1)),
+ [1.0, 0.0])
+ npt.assert_allclose(render_subwoofers(positions, cart(30, -30, 1)),
+ [1.0, 0.0])
+ npt.assert_allclose(render_subwoofers(positions, cart(-30, 0, 1)),
+ [0.0, 1.0])
+ npt.assert_allclose(render_subwoofers(positions, cart(-30, -30, 1)),
+ [0.0, 1.0])
+ npt.assert_allclose(render_subwoofers(positions, cart(180, 0, 1)),
+ [0.5, 0.5])
+
+
+def test_render_onesub():
+ positions = np.array([
+ cart(0, -30, 1),
+ ])
+ npt.assert_allclose(render_subwoofers(positions, cart(15, 3, 1)),
+ [1.0])
+
+
+def test_upmix():
+ layout = bs2051.get_layout("0+5+0")
+ M = subwoofer_upmix_matrix(layout)
+
+ ident = np.eye(5)
+ npt.assert_allclose(M, np.vstack((ident[:3], np.ones((1, 5)), ident[3:])))
+ npt.assert_allclose(np.dot(M, [1, 0, 0, 0, 0]), [1, 0, 0, 1, 0, 0])
+
+
+def test_downmix():
+ layout = bs2051.get_layout("0+5+0")
+ M = lfe_downmix_matrix(layout)
+
+ npt.assert_allclose(M[layout.is_lfe], 1)
+ npt.assert_allclose(M[~layout.is_lfe], 0)
diff --git a/ear/core/util.py b/ear/core/util.py
new file mode 100644
index 00000000..2eaa83be
--- /dev/null
+++ b/ear/core/util.py
@@ -0,0 +1,67 @@
+import numpy as np
+from attr import attrs, attrib
+
+
+def has_shape(*shape):
+ """Attrs validator that checks that a numpy array has the given shape.
+
+ Parameters:
+ *shape: shape to match against; any elements that are None are ignored
+ and may be any length.
+
+ Returns:
+ function: Validation function as rquired by the attr.attrib validator
+ argument.
+
+ Example:
+ >>> @attrs
+ ... class Test(object):
+ ... x = attrib(validator=has_shape(None, 2))
+ >>> Test(np.array([[1,2]]))
+ Test(x=array([[1, 2]]))
+ >>> Test(np.array([]))
+ Traceback (most recent call last):
+ ...
+ ValueError: ("'x' must be of shape (None, 2) which array([]...
+ """
+ def f(inst, attr, value):
+ if (len(value.shape) != len(shape) or
+ any(dim_b is not None and dim_a != dim_b
+ for dim_a, dim_b in zip(value.shape, shape))):
+ raise ValueError(
+ "'{name}' must be of shape {shape} which {value!r} isn't."
+ .format(name=attr.name, shape=shape, value=value),
+ attr, shape, value,
+ )
+ return f
+
+
+def as_array(**kwargs):
+ """Make an attrs conversion function that calls np.asarray with the
+ provided arguments.
+
+ Example:
+ >>> @attrs
+ ... class Test(object):
+ ... x = attrib(convert=as_array(dtype=float))
+ >>> Test([1])
+ Test(x=array([1.]))
+ """
+ def f(x):
+ return np.asarray(x, **kwargs)
+ return f
+
+
+def safe_norm_position(position):
+ """
+ Parameters:
+ position (array of shape (3,)): Position to normalise.
+
+ Returns:
+ array of shape (3,): normalised position
+ """
+ norm = np.linalg.norm(position)
+ if norm < 1e-10:
+ return np.array([0.0, 1.0, 0.0])
+ else:
+ return position / norm
diff --git a/ear/doc/layout_files/bbc_listening_room.yaml b/ear/doc/layout_files/bbc_listening_room.yaml
new file mode 100644
index 00000000..94cf3bbf
--- /dev/null
+++ b/ear/doc/layout_files/bbc_listening_room.yaml
@@ -0,0 +1,35 @@
+speakers:
+ - {channel: 0, names: B+045, position: {az: 45.0, el: -22.0, r: 2.58975}}
+ - {channel: 1, names: B+000, position: {az: 0.0, el: -26.0, r: 2.2 }}
+ - {channel: 2, names: B-045, position: {az: -45.0, el: -22.0, r: 2.58975}}
+ - {channel: 3, names: B-135, position: {az: -135.0, el: -22.0, r: 2.58975}}
+ - {channel: 4, names: B+135, position: {az: 135.0, el: -22.0, r: 2.58975}}
+ - {channel: 5, names: M+045, position: {az: 45.0, el: 0.0, r: 3.00613}}
+ - {channel: 6, names: M+030, position: {az: 30.0, el: 0.0, r: 2.37201}}
+ - {channel: 7, names: M+000, position: {az: 0.0, el: 0.0, r: 1.994 }}
+ - {channel: 8, names: M-030, position: {az: -30.0, el: 0.0, r: 2.37201}}
+ - {channel: 9, names: M-045, position: {az: -45.0, el: 0.0, r: 3.00613}}
+ - {channel: 10, names: M-060, position: {az: -60.0, el: 0.0, r: 2.70687}}
+ - {channel: 11, names: M-090, position: {az: -90.0, el: 0.0, r: 2.284 }}
+ - {channel: 12, names: M-110, position: {az: -110.0, el: 0.0, r: 2.45943}}
+ - {channel: 13, names: M-135, position: {az: -135.0, el: 0.0, r: 3.00613}}
+ - {channel: 14, names: M+180, position: {az: 180.0, el: 0.0, r: 1.994 }}
+ - {channel: 15, names: M+135, position: {az: 135.0, el: 0.0, r: 3.00613}}
+ - {channel: 16, names: M+110, position: {az: 110.0, el: 0.0, r: 2.45943}}
+ - {channel: 17, names: M+090, position: {az: 90.0, el: 0.0, r: 2.284 }}
+ - {channel: 18, names: M+060, position: {az: 60.0, el: 0.0, r: 2.70687}}
+ - {channel: 19, names: U+045, position: {az: 45.0, el: 40.0, r: 1.91029}}
+ - {channel: 20, names: U+030, position: {az: 30.0, el: 40.0, r: 1.91029}}
+ - {channel: 21, names: U+000, position: {az: 0.0, el: 40.0, r: 1.91029}}
+ - {channel: 22, names: U-030, position: {az: -30.0, el: 40.0, r: 1.91029}}
+ - {channel: 23, names: U-045, position: {az: -45.0, el: 40.0, r: 1.91029}}
+ - {channel: 24, names: U-090, position: {az: -90.0, el: 40.0, r: 1.86906}}
+ - {channel: 25, names: U-110, position: {az: -110.0, el: 40.0, r: 1.91029}}
+ - {channel: 26, names: U-135, position: {az: -135.0, el: 40.0, r: 1.91029}}
+ - {channel: 27, names: [U+180, UH+180], position: {az: 180.0, el: 40.0, r: 1.91029}}
+ - {channel: 28, names: U+135, position: {az: 135.0, el: 40.0, r: 1.91029}}
+ - {channel: 29, names: U+110, position: {az: 110.0, el: 40.0, r: 1.91029}}
+ - {channel: 30, names: U+090, position: {az: 90.0, el: 40.0, r: 1.86906}}
+ - {channel: 31, names: T+000, position: {az: 0.0, el: 90.0, r: 1.297 }}
+ - {channel: 32, names: LFE1, position: {az: 17.0, el: -25.0, r: 2.29 }, gain_linear: 0.316228}
+ - {channel: 33, names: LFE2, position: {az: -163.0, el: -25.0, r: 2.29 }, gain_linear: 0.316228}
diff --git a/ear/fileio/__init__.py b/ear/fileio/__init__.py
new file mode 100644
index 00000000..7fb06fbe
--- /dev/null
+++ b/ear/fileio/__init__.py
@@ -0,0 +1 @@
+from .utils import openBw64, openBw64Adm # noqa: F401
diff --git a/ear/fileio/adm/__init__.py b/ear/fileio/adm/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/fileio/adm/adm.py b/ear/fileio/adm/adm.py
new file mode 100644
index 00000000..4f451619
--- /dev/null
+++ b/ear/fileio/adm/adm.py
@@ -0,0 +1,147 @@
+from itertools import chain
+import warnings
+from collections import OrderedDict
+from six import iteritems
+from .exceptions import AdmIDError, AdmIDWarning
+
+
+class ADM(object):
+
+ def __init__(self):
+ self._ap = []
+ self._ac = []
+ self._ao = []
+ self._apf = []
+ self._acf = []
+ self._asf = []
+ self._atf = []
+ self._atu = []
+
+ self._object_lists = (
+ self._ap,
+ self._ac,
+ self._ao,
+ self._apf,
+ self._acf,
+ self._asf,
+ self._atf,
+ self._atu)
+
+ @classmethod
+ def _without_duplicates(cls, obj_list):
+ """Remove objects with duplicate IDs.
+
+ For each id, there should only be one object with that id. If there are
+ two objects, and one is a common definition and one isn't, then a
+ warning is issued and the non-common definition is used.
+ """
+ by_id = OrderedDict()
+
+ for obj in obj_list:
+ if obj.id is None:
+ yield obj
+ else:
+ by_id.setdefault(obj.id.upper(), []).append(obj)
+
+ for id, objects in iteritems(by_id):
+ common = [obj for obj in objects if obj.is_common_definition]
+ non_common = [obj for obj in objects if not obj.is_common_definition]
+
+ # check for errors
+ assert len(common) <= 1, "duplicate common definitions found"
+ if len(non_common) > 1:
+ raise AdmIDError("duplicate objects with id={id}".format(id=id))
+ assert common or non_common
+
+ if non_common:
+ if common:
+ warnings.warn("non-common-definition element found with id {id} that overrides a common-definition element".format(id=id),
+ AdmIDWarning)
+ yield non_common[0]
+ else:
+ yield common[0]
+
+ def lazy_lookup_references(self):
+ for obj_list in self._object_lists:
+ obj_list[:] = self._without_duplicates(obj_list)
+
+ for element in self.elements:
+ element.lazy_lookup_references()
+
+ def addAudioProgramme(self, programme):
+ programme.adm_parent = self
+ self._ap.append(programme)
+
+ def addAudioContent(self, content):
+ content.adm_parent = self
+ self._ac.append(content)
+
+ def addAudioObject(self, audioobject):
+ audioobject.adm_parent = self
+ self._ao.append(audioobject)
+
+ def addAudioPackFormat(self, packformat):
+ packformat.adm_parent = self
+ self._apf.append(packformat)
+
+ def addAudioChannelFormat(self, channelformat):
+ channelformat.adm_parent = self
+ self._acf.append(channelformat)
+
+ def addAudioStreamFormat(self, streamformat):
+ streamformat.adm_parent = self
+ self._asf.append(streamformat)
+
+ def addAudioTrackFormat(self, trackformat):
+ trackformat.adm_parent = self
+ self._atf.append(trackformat)
+
+ def addAudioTrackUID(self, trackUID):
+ trackUID.adm_parent = self
+ self._atu.append(trackUID)
+
+ @property
+ def elements(self):
+ return chain(*self._object_lists)
+
+ def __getitem__(self, key):
+ return self.lookup_element(key)
+
+ def lookup_element(self, key):
+ key_upper = key.upper()
+ for element in self.elements:
+ if element.id.upper() == key_upper:
+ return element
+ raise KeyError('Unkown element requested {0}'.format(key))
+
+ @property
+ def audioProgrammes(self):
+ return self._ap
+
+ @property
+ def audioContents(self):
+ return self._ac
+
+ @property
+ def audioObjects(self):
+ return self._ao
+
+ @property
+ def audioPackFormats(self):
+ return self._apf
+
+ @property
+ def audioChannelFormats(self):
+ return self._acf
+
+ @property
+ def audioStreamFormats(self):
+ return self._asf
+
+ @property
+ def audioTrackFormats(self):
+ return self._atf
+
+ @property
+ def audioTrackUIDs(self):
+ return self._atu
diff --git a/ear/fileio/adm/builder.py b/ear/fileio/adm/builder.py
new file mode 100644
index 00000000..e4864b45
--- /dev/null
+++ b/ear/fileio/adm/builder.py
@@ -0,0 +1,304 @@
+from attr import attrs, attrib, Factory
+from .adm import ADM
+from .elements import AudioProgramme, AudioContent, AudioObject
+from .elements import TypeDefinition, FormatDefinition
+from .elements import AudioChannelFormat, AudioPackFormat, AudioTrackFormat, AudioTrackUID, AudioStreamFormat
+
+DEFAULT = object()
+
+
+@attrs
+class ADMBuilder(object):
+ """Builder object for creating ADM object graphs.
+
+ Attributes:
+ adm (ADM): ADM object to modify.
+ last_programme (AudioProgramme): The last programme created, to which
+ created audioContents will be linked.
+ last_content (AudioContent): The last content created, to which created
+ audioObjects will be linked by default.
+ last_object (AudioObject): The last object created, to which created
+ audioObjects or audioPackFormats will be linked by default.
+ last_pack_format (AudioPackFormat): The last pack_format created, to which
+ created audioChannelFormats will be linked by default.
+ last_stream_format (AudioStreamFormat): The last stream_format created, to
+ which created audioTrackFormats will be linked by default.
+ """
+ adm = attrib(default=Factory(ADM))
+ last_programme = attrib(default=None)
+ last_content = attrib(default=None)
+ last_object = attrib(default=None)
+ last_pack_format = attrib(default=None)
+ last_stream_format = attrib(default=None)
+ item_parent = attrib(default=None)
+
+ def load_common_definitions(self):
+ """Load common definitions into adm."""
+ from .common_definitions import load_common_definitions
+ load_common_definitions(self.adm)
+
+ def create_programme(self, **kwargs):
+ """Create a new audioProgramme.
+
+ Args:
+ kwargs: see AudioProgramme
+
+ Returns:
+ AudioProgramme: created programme
+ """
+ programme = AudioProgramme(**kwargs)
+ self.adm.addAudioProgramme(programme)
+
+ self.last_programme = programme
+
+ return programme
+
+ def create_content(self, parent=DEFAULT, **kwargs):
+ """Create a new audioContent.
+
+ Args:
+ parent (AudioProgramme): parent programme; defaults to the last one created
+ kwargs: see AudioContent
+
+ Returns:
+ AudioContent: created content
+ """
+ content = AudioContent(**kwargs)
+ self.adm.addAudioContent(content)
+
+ if parent is DEFAULT:
+ parent = self.last_programme
+ if parent is not None:
+ parent.audioContents.append(content)
+
+ self.last_content = content
+ self.item_parent = content
+
+ return content
+
+ def create_object(self, parent=DEFAULT, **kwargs):
+ """Create a new audioObject.
+
+ Args:
+ parent (AudioContent or AudioObject): parent content or object;
+ defaults to the last content created
+ kwargs: see AudioObject
+
+ Returns:
+ AudioObject: created object
+ """
+ object = AudioObject(**kwargs)
+ self.adm.addAudioObject(object)
+
+ if parent is DEFAULT:
+ parent = self.last_content
+ if parent is not None:
+ parent.audioObjects.append(object)
+
+ self.item_parent = object
+ self.last_object = object
+
+ return object
+
+ def create_pack(self, parent=DEFAULT, **kwargs):
+ """Create a new audioPackFormat.
+
+ Args:
+ parent (AudioObject or AudioPackFormat): parent object or packFormat;
+ defaults to the last object created
+ kwargs: see AudioPackFormat
+
+ Returns:
+ AudioPackFormat: created pack_format
+ """
+ pack_format = AudioPackFormat(**kwargs)
+ self.adm.addAudioPackFormat(pack_format)
+
+ if parent is DEFAULT:
+ parent = self.last_object
+ if parent is not None:
+ parent.audioPackFormats.append(pack_format)
+
+ self.last_pack_format = pack_format
+
+ return pack_format
+
+ def create_channel(self, parent=DEFAULT, **kwargs):
+ """Create a new audioChannelFormat.
+
+ Args:
+ parent (AudioPackFormat): parent packFormat;
+ defaults to the last packFormat created
+ kwargs: see AudioChannelFormat
+
+ Returns:
+ AudioChannelFormat: created channel_format
+ """
+ channel_format = AudioChannelFormat(**kwargs)
+ self.adm.addAudioChannelFormat(channel_format)
+
+ if parent is DEFAULT:
+ parent = self.last_pack_format
+ if parent is not None:
+ parent.audioChannelFormats.append(channel_format)
+
+ return channel_format
+
+ def create_stream(self, **kwargs):
+ """Create a new audioStreamFormat.
+
+ Args:
+ kwargs: see AudioChannelFormat
+
+ Returns:
+ AudioStreamFormat: created stream_format
+ """
+ stream_format = AudioStreamFormat(**kwargs)
+ self.adm.addAudioStreamFormat(stream_format)
+
+ self.last_stream_format = stream_format
+
+ return stream_format
+
+ def create_track(self, parent=DEFAULT, **kwargs):
+ """Create a new audioTrackFormat.
+
+ Args:
+ parent (AudioStreamFormat): parent streamFormat;
+ defaults to the last audioStreamFormat created
+ kwargs: see AudioTrackFormat
+
+ Returns:
+ AudioTrackFormat: created track_format
+ """
+ track_format = AudioTrackFormat(**kwargs)
+ self.adm.addAudioTrackFormat(track_format)
+
+ if parent is DEFAULT:
+ parent = self.last_stream_format
+ if parent is not None:
+ parent.audioTrackFormats.append(track_format)
+
+ return track_format
+
+ def create_track_uid(self, parent=DEFAULT, **kwargs):
+ """Create a new audioTrackUID.
+
+ Args:
+ parent (AudioObject): parent audioObject;
+ defaults to the last audioObject created
+ kwargs: see AudioTrackUID
+
+ Returns:
+ AudioTrackUID: created track_uid
+ """
+ track_uid = AudioTrackUID(**kwargs)
+ self.adm.addAudioTrackUID(track_uid)
+
+ if parent is DEFAULT:
+ parent = self.last_object
+ if parent is not None:
+ parent.audioTrackUIDs.append(track_uid)
+
+ return track_uid
+
+ @attrs
+ class MonoItem(object):
+ """Structure referencing the ADM components created as part of a mono item."""
+ channel_format = attrib()
+ track_format = attrib()
+ pack_format = attrib()
+ stream_format = attrib()
+ track_uid = attrib()
+ audio_object = attrib()
+ parent = attrib()
+
+ def create_item_mono(self, type, track_index, name, parent=DEFAULT, block_formats=[]):
+ """Create ADM components needed to represent a mono channel, either
+ DirectSpeakers or Objects.
+
+ Args:
+ type (TypeDefinition): type of channelFormat and packFormat
+ track_index (int): zero-based index of the track in the BWF file.
+ name (str): name used for all components
+ parent (AudioContent or AudioObject): parent of the created audioObject
+ defaults to the last content or explicitly created object
+ block_formats (list of AudioBlockFormat): block formats to add to
+ the channel format
+
+ Returns:
+ MonoItem: the created components
+ """
+ channel_format = AudioChannelFormat(
+ audioChannelFormatName=name,
+ type=type,
+ audioBlockFormats=block_formats)
+ self.adm.addAudioChannelFormat(channel_format)
+
+ track_format = AudioTrackFormat(
+ audioTrackFormatName=name,
+ format=FormatDefinition.PCM,
+ )
+ self.adm.addAudioTrackFormat(track_format)
+
+ pack_format = AudioPackFormat(
+ audioPackFormatName=name,
+ type=type,
+ audioChannelFormats=[channel_format],
+ )
+ self.adm.addAudioPackFormat(pack_format)
+
+ stream_format = AudioStreamFormat(
+ audioStreamFormatName=name,
+ format=FormatDefinition.PCM,
+ audioTrackFormats=[track_format],
+ audioChannelFormat=channel_format,
+ )
+ self.adm.addAudioStreamFormat(stream_format)
+
+ track_uid = AudioTrackUID(
+ trackIndex=track_index + 1,
+ audioTrackFormat=track_format,
+ audioPackFormat=pack_format,
+ )
+ self.adm.addAudioTrackUID(track_uid)
+
+ audio_object = AudioObject(
+ audioObjectName=name,
+ audioPackFormats=[pack_format],
+ audioTrackUIDs=[track_uid],
+ )
+ self.adm.addAudioObject(audio_object)
+
+ if parent is DEFAULT:
+ parent = self.item_parent
+ if parent is not None:
+ parent.audioObjects.append(audio_object)
+
+ self.last_object = audio_object
+ self.last_pack_format = pack_format
+ self.last_stream_format = stream_format
+
+ return self.MonoItem(
+ channel_format=channel_format,
+ track_format=track_format,
+ pack_format=pack_format,
+ stream_format=stream_format,
+ track_uid=track_uid,
+ audio_object=audio_object,
+ parent=parent,
+ )
+
+ def create_item_objects(self, *args, **kwargs):
+ """Create ADM components needed to represent an object channel.
+
+ Wraps create_item_mono with type=TypeDefinition.Objects.
+ """
+ return self.create_item_mono(TypeDefinition.Objects, *args, **kwargs)
+
+ def create_item_direct_speakers(self, *args, **kwargs):
+ """Create ADM components needed to represent a DirectSpeakers channel.
+
+ Wraps create_item_mono with type=TypeDefinition.DirectSpeakers.
+ """
+ return self.create_item_mono(TypeDefinition.DirectSpeakers, *args, **kwargs)
diff --git a/ear/fileio/adm/chna.py b/ear/fileio/adm/chna.py
new file mode 100644
index 00000000..5c191d79
--- /dev/null
+++ b/ear/fileio/adm/chna.py
@@ -0,0 +1,107 @@
+import re
+from .elements import AudioTrackUID
+from ..bw64.chunks import AudioID
+
+
+def load_chna_chunk(adm, chna):
+ """Add information from the chna chunk to the adm track UIDs structure.
+
+ Any existing track UIDs in adm are checked for consistency against the data
+ in chna; any non-existent track-UIDs are created.
+
+ The track index, pack format ref and track format ref attributes are
+ transferred; there references are resolved, and we assume that any
+ references in adm have already been resolved
+
+ Parameters:
+ adm (ADM): adm structure to add information to
+ chna (ChnaChunk): chna chunk to get information from
+ """
+ track_uid_by_id = {track.id.upper(): track for track in adm.audioTrackUIDs}
+
+ for chna_entry in chna.audioIDs:
+ track = track_uid_by_id.get(chna_entry.audioTrackUID.upper())
+
+ if track is None:
+ track = AudioTrackUID(id=chna_entry.audioTrackUID.upper())
+ adm.addAudioTrackUID(track)
+
+ if track.trackIndex is None:
+ track.trackIndex = chna_entry.trackIndex
+ else:
+ assert track.trackIndex == chna_entry.trackIndex
+
+ if track.audioTrackFormat is None:
+ track.audioTrackFormatIDRef = chna_entry.audioTrackFormatIDRef
+ elif track.audioTrackFormat.id.upper() != chna_entry.audioTrackFormatIDRef.upper():
+ raise Exception("Error in track UID {track.id}: audioTrackFormatIDRef in CHNA, '{chna_entry.audioTrackFormatIDRef}' "
+ "does not match value in AXML, '{track.audioTrackFormat.id}'.".format(
+ track=track, chna_entry=chna_entry))
+
+ if chna_entry.audioPackFormatIDRef is not None:
+ if track.audioPackFormat is None:
+ track.audioPackFormatIDRef = chna_entry.audioPackFormatIDRef
+ elif track.audioPackFormat.id.upper() != chna_entry.audioPackFormatIDRef.upper():
+ raise Exception("Error in track UID {track.id}: audioPackFormatIDRef in CHNA, '{chna_entry.audioPackFormatIDRef}' "
+ "does not match value in AXML, '{track.audioPackFormat.id}'.".format(
+ track=track, chna_entry=chna_entry))
+
+ adm.lazy_lookup_references()
+
+
+def _get_chna_entries(adm):
+ for track_uid in adm.audioTrackUIDs:
+ if track_uid.trackIndex is None:
+ raise Exception("Track UID {track_uid.id} has no track number.".format(track_uid=track_uid))
+ if track_uid.audioTrackFormat is None:
+ raise Exception("Track UID {track_uid.id} has no track format.".format(track_uid=track_uid))
+
+ assert track_uid.id is not None, "ids have not been generated"
+ assert track_uid.audioTrackFormat.id is not None, "ids have not been generated"
+ assert track_uid.audioPackFormat is None or track_uid.audioPackFormat.id is not None, "ids have not been generated"
+
+ yield AudioID(
+ trackIndex=track_uid.trackIndex,
+ audioTrackUID=track_uid.id,
+ audioTrackFormatIDRef=track_uid.audioTrackFormat.id,
+ audioPackFormatIDRef=(track_uid.audioPackFormat.id
+ if track_uid.audioPackFormat is not None
+ else None))
+
+
+def populate_chna_chunk(chna, adm):
+ """Populate the CHNA chunk with information from the ADM model.
+
+ All CHNA entries are replaced, and are populated from the trackIndex,
+ audioTrackUID, and the track format/pack format references.
+
+ Since the CHNA chunk contains IDs, the IDs in the ADM must have been
+ generated before calling this.
+
+ Parameters:
+ adm (ADM): adm structure to get information to
+ chna (ChnaChunk): chna chunk to populate
+ """
+ chna.audioIDs = list(_get_chna_entries(adm))
+
+
+_TRACK_UID_RE = re.compile("ATU_([0-9a-fA-F]{8})$")
+
+
+def guess_track_indices(adm):
+ """Guess track indices from the audioTrackUID IDs.
+
+ This information should really come from the CHNA, but sometimes that isn't
+ available.
+
+ Parameters:
+ adm (ADM): ADM structure to modify
+ """
+ for track_uid in adm.audioTrackUIDs:
+ assert track_uid.trackIndex is None
+
+ match = _TRACK_UID_RE.match(track_uid.id)
+ if match is None:
+ raise Exception("Invalid track UID {}.".format(track_uid.id))
+
+ track_uid.trackIndex = int(match.group(1), 16)
diff --git a/ear/fileio/adm/common_definitions.py b/ear/fileio/adm/common_definitions.py
new file mode 100644
index 00000000..92eaa19d
--- /dev/null
+++ b/ear/fileio/adm/common_definitions.py
@@ -0,0 +1,10 @@
+import pkg_resources
+from .xml import parse_adm_elements
+import lxml.etree
+
+
+def load_common_definitions(adm):
+ fname = "data/2094_common_definitions.xml"
+ with pkg_resources.resource_stream(__name__, fname) as stream:
+ element = lxml.etree.parse(stream)
+ parse_adm_elements(adm, element, common_definitions=True)
diff --git a/ear/fileio/adm/data/2094_common_definitions.xml b/ear/fileio/adm/data/2094_common_definitions.xml
new file mode 100644
index 00000000..0599327e
--- /dev/null
+++ b/ear/fileio/adm/data/2094_common_definitions.xml
@@ -0,0 +1,4677 @@
+
+
+
+
+
+
+ AC_00010003
+
+
+ AC_00010001
+ AC_00010002
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010009
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010005
+ AC_00010006
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_00010009
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_00010026
+ AC_00010027
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_0001000a
+ AC_0001000b
+ AC_0001001c
+ AC_0001001d
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_0001000d
+ AC_0001000f
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_00010024
+ AC_00010025
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_00010013
+ AC_00010014
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_00010013
+ AC_00010014
+ AC_00010024
+ AC_00010025
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_0001000b
+ AC_0001000c
+ AC_0001001c
+ AC_0001001d
+ AC_00010013
+ AC_00010014
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_0001000d
+ AC_0001000f
+ AC_00010010
+ AC_00010012
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_0001000d
+ AC_0001000f
+ AC_00010010
+ AC_00010012
+ AC_00010015
+
+
+ AC_00010003
+ AC_00010001
+ AC_00010002
+ AC_00010022
+ AC_00010023
+ AC_0001000a
+ AC_0001000b
+ AC_0001001c
+ AC_0001001d
+ AC_00010028
+ AC_00010020
+ AC_00010021
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_0001000d
+ AC_0001000f
+ AC_00010010
+ AC_00010012
+ AC_00010024
+ AC_00010025
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_0001000a
+ AC_0001000b
+ AC_0001001c
+ AC_0001001d
+ AC_00010022
+ AC_00010023
+ AC_0001001e
+ AC_0001001f
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_0001000a
+ AC_0001000b
+ AC_0001001c
+ AC_0001001d
+ AC_00010022
+ AC_00010023
+ AC_0001001e
+ AC_0001001f
+ AC_00010024
+ AC_00010025
+
+
+ AC_00010018
+ AC_00010019
+ AC_00010003
+ AC_00010020
+ AC_0001001c
+ AC_0001001d
+ AC_00010001
+ AC_00010002
+ AC_00010009
+ AC_00010021
+ AC_0001000a
+ AC_0001000b
+ AC_00010022
+ AC_00010023
+ AC_0001000e
+ AC_0001000c
+ AC_0001001e
+ AC_0001001f
+ AC_00010013
+ AC_00010014
+ AC_00010011
+ AC_00010015
+ AC_00010016
+ AC_00010017
+
+
+ AC_00010001
+ AC_00010002
+ AC_00010003
+ AC_00010004
+ AC_00010005
+ AC_00010006
+ AC_0001000a
+ AC_0001000b
+ AC_0001001a
+ AC_0001001b
+ AC_0001000d
+ AC_0001000f
+ AC_0001000e
+ AC_00010010
+ AC_00010012
+ AC_00010013
+ AC_00010014
+ AC_0001001e
+ AC_0001001f
+
+
+ AC_00050001
+ AC_00050002
+
+
+ AC_00040001
+ AC_00040002
+ AC_00040003
+ AC_00040004
+
+
+ AC_00040005
+ AC_00040006
+ AC_00040007
+ AC_00040008
+ AC_00040009
+ AP_00040001
+
+
+ AC_0004000a
+ AC_0004000b
+ AC_0004000c
+ AC_0004000d
+ AC_0004000e
+ AC_0004000f
+ AC_00040010
+ AP_00040002
+
+
+ AC_00040011
+ AC_00040012
+ AC_00040013
+ AC_00040014
+ AC_00040015
+ AC_00040016
+ AC_00040017
+ AC_00040018
+ AC_00040019
+ AP_00040003
+
+
+ AC_0004001a
+ AC_0004001b
+ AC_0004001c
+ AC_0004001d
+ AC_0004001e
+ AC_0004001f
+ AC_00040020
+ AC_00040021
+ AC_00040022
+ AC_00040023
+ AC_00040024
+ AP_00040004
+
+
+ AC_00040025
+ AC_00040026
+ AC_00040027
+ AC_00040028
+ AC_00040029
+ AC_0004002a
+ AC_0004002b
+ AC_0004002c
+ AC_0004002d
+ AC_0004002e
+ AC_0004002f
+ AC_00040030
+ AC_00040031
+ AP_00040005
+
+
+ AC_00040101
+ AC_00040102
+ AC_00040103
+ AC_00040104
+
+
+ AC_00040105
+ AC_00040106
+ AC_00040107
+ AC_00040108
+ AC_00040109
+ AP_00040011
+
+
+ AC_0004010a
+ AC_0004010b
+ AC_0004010c
+ AC_0004010d
+ AC_0004010e
+ AC_0004010f
+ AC_00040110
+ AP_00040012
+
+
+ AC_00040111
+ AC_00040112
+ AC_00040113
+ AC_00040114
+ AC_00040115
+ AC_00040116
+ AC_00040117
+ AC_00040118
+ AC_00040119
+ AP_00040013
+
+
+ AC_0004011a
+ AC_0004011b
+ AC_0004011c
+ AC_0004011d
+ AC_0004011e
+ AC_0004011f
+ AC_00040120
+ AC_00040121
+ AC_00040122
+ AC_00040123
+ AC_00040124
+ AP_00040014
+
+
+ AC_00040125
+ AC_00040126
+ AC_00040127
+ AC_00040128
+ AC_00040129
+ AC_0004012a
+ AC_0004012b
+ AC_0004012c
+ AC_0004012d
+ AC_0004012e
+ AC_0004012f
+ AC_00040130
+ AC_00040131
+ AP_00040015
+
+
+ AC_00040201
+ AC_00040202
+ AC_00040203
+ AC_00040204
+
+
+ AC_00040205
+ AC_00040206
+ AC_00040207
+ AC_00040208
+ AC_00040209
+ AP_00040021
+
+
+ AC_0004020a
+ AC_0004020b
+ AC_0004020c
+ AC_0004020d
+ AC_0004020e
+ AC_0004020f
+ AC_00040210
+ AP_00040022
+
+
+ AC_00040101
+ AC_00040102
+ AC_00040104
+
+
+ AC_00040105
+ AC_00040109
+ AP_00040111
+
+
+ AC_00040105
+ AC_00040109
+ AP_00040011
+
+
+ AC_0004010a
+ AC_00040110
+ AP_00040210
+
+
+ AC_00040105
+ AC_00040106
+ AC_00040108
+ AC_00040109
+ AP_00040011
+
+
+
+ urn:itu:bs:2051:0:speaker:M+030
+ 30.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-030
+ -30.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+000
+ 0.0
+ 0.0
+ 1.0
+
+
+
+ 120.0
+
+ urn:itu:bs:2051:0:speaker:LFE
+ 0.0
+ -30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+110
+ 110.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-110
+ -110.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+022
+ 22.5
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-022
+ -22.5
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+180
+ 180.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+090
+ 90.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-090
+ -90.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:T+000
+ 0.0
+ 90.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U+030
+ 30.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U+000
+ 0.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U-030
+ -30.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U+110
+ 110.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U+180
+ 180.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U-110
+ -110.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U+090
+ 90.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U-090
+ -90.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:B+000
+ 0.0
+ -30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:B+045
+ 45.0
+ -30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:B-045
+ -45.0
+ -30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+060
+ 60.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-060
+ -60.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+135_Diff
+ 135.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-135_Diff
+ -135.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+135
+ 135.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-135
+ -135.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U+135
+ 135.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U-135
+ -135.0
+ 30.0
+ 1.0
+
+
+
+ 120.0
+
+ urn:itu:bs:2051:0:speaker:LFEL
+ 45.0
+ -30.0
+ 1.0
+
+
+
+ 120.0
+
+ urn:itu:bs:2051:0:speaker:LFER
+ -45.0
+ -30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U+045
+ 45.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:U-045
+ -45.0
+ 30.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+SC
+ 25.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-SC
+ -25.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M+045
+ 45.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:M-045
+ -45.0
+ 0.0
+ 1.0
+
+
+
+
+ urn:itu:bs:2051:0:speaker:UH+180
+ 180.0
+ 45.0
+ 1.0
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+ SN3D
+
+
+
+
+ -1
+ 1
+ SN3D
+
+
+
+
+ 0
+ 1
+ SN3D
+
+
+
+
+ 1
+ 1
+ SN3D
+
+
+
+
+ -2
+ 2
+ SN3D
+
+
+
+
+ -1
+ 2
+ SN3D
+
+
+
+
+ 0
+ 2
+ SN3D
+
+
+
+
+ 1
+ 2
+ SN3D
+
+
+
+
+ 2
+ 2
+ SN3D
+
+
+
+
+ -3
+ 3
+ SN3D
+
+
+
+
+ -2
+ 3
+ SN3D
+
+
+
+
+ -1
+ 3
+ SN3D
+
+
+
+
+ 0
+ 3
+ SN3D
+
+
+
+
+ 1
+ 3
+ SN3D
+
+
+
+
+ 2
+ 3
+ SN3D
+
+
+
+
+ 3
+ 3
+ SN3D
+
+
+
+
+ -4
+ 4
+ SN3D
+
+
+
+
+ -3
+ 4
+ SN3D
+
+
+
+
+ -2
+ 4
+ SN3D
+
+
+
+
+ -1
+ 4
+ SN3D
+
+
+
+
+ 0
+ 4
+ SN3D
+
+
+
+
+ 1
+ 4
+ SN3D
+
+
+
+
+ 2
+ 4
+ SN3D
+
+
+
+
+ 3
+ 4
+ SN3D
+
+
+
+
+ 4
+ 4
+ SN3D
+
+
+
+
+ -5
+ 5
+ SN3D
+
+
+
+
+ -4
+ 5
+ SN3D
+
+
+
+
+ -3
+ 5
+ SN3D
+
+
+
+
+ -2
+ 5
+ SN3D
+
+
+
+
+ -1
+ 5
+ SN3D
+
+
+
+
+ 0
+ 5
+ SN3D
+
+
+
+
+ 1
+ 5
+ SN3D
+
+
+
+
+ 2
+ 5
+ SN3D
+
+
+
+
+ 3
+ 5
+ SN3D
+
+
+
+
+ 4
+ 5
+ SN3D
+
+
+
+
+ 5
+ 5
+ SN3D
+
+
+
+
+ -6
+ 6
+ SN3D
+
+
+
+
+ -5
+ 6
+ SN3D
+
+
+
+
+ -4
+ 6
+ SN3D
+
+
+
+
+ -3
+ 6
+ SN3D
+
+
+
+
+ -2
+ 6
+ SN3D
+
+
+
+
+ -1
+ 6
+ SN3D
+
+
+
+
+ 0
+ 6
+ SN3D
+
+
+
+
+ 1
+ 6
+ SN3D
+
+
+
+
+ 2
+ 6
+ SN3D
+
+
+
+
+ 3
+ 6
+ SN3D
+
+
+
+
+ 4
+ 6
+ SN3D
+
+
+
+
+ 5
+ 6
+ SN3D
+
+
+
+
+ 6
+ 6
+ SN3D
+
+
+
+
+ -7
+ 7
+ SN3D
+
+
+
+
+ -6
+ 7
+ SN3D
+
+
+
+
+ -5
+ 7
+ SN3D
+
+
+
+
+ -4
+ 7
+ SN3D
+
+
+
+
+ -3
+ 7
+ SN3D
+
+
+
+
+ -2
+ 7
+ SN3D
+
+
+
+
+ -1
+ 7
+ SN3D
+
+
+
+
+ 0
+ 7
+ SN3D
+
+
+
+
+ 1
+ 7
+ SN3D
+
+
+
+
+ 2
+ 7
+ SN3D
+
+
+
+
+ 3
+ 7
+ SN3D
+
+
+
+
+ 4
+ 7
+ SN3D
+
+
+
+
+ 5
+ 7
+ SN3D
+
+
+
+
+ 6
+ 7
+ SN3D
+
+
+
+
+ 7
+ 7
+ SN3D
+
+
+
+
+ -8
+ 8
+ SN3D
+
+
+
+
+ -7
+ 8
+ SN3D
+
+
+
+
+ -6
+ 8
+ SN3D
+
+
+
+
+ -5
+ 8
+ SN3D
+
+
+
+
+ -4
+ 8
+ SN3D
+
+
+
+
+ -3
+ 8
+ SN3D
+
+
+
+
+ -2
+ 8
+ SN3D
+
+
+
+
+ -1
+ 8
+ SN3D
+
+
+
+
+ 0
+ 8
+ SN3D
+
+
+
+
+ 1
+ 8
+ SN3D
+
+
+
+
+ 2
+ 8
+ SN3D
+
+
+
+
+ 3
+ 8
+ SN3D
+
+
+
+
+ 4
+ 8
+ SN3D
+
+
+
+
+ 5
+ 8
+ SN3D
+
+
+
+
+ 6
+ 8
+ SN3D
+
+
+
+
+ 7
+ 8
+ SN3D
+
+
+
+
+ 8
+ 8
+ SN3D
+
+
+
+
+ -9
+ 9
+ SN3D
+
+
+
+
+ -8
+ 9
+ SN3D
+
+
+
+
+ -7
+ 9
+ SN3D
+
+
+
+
+ -6
+ 9
+ SN3D
+
+
+
+
+ -5
+ 9
+ SN3D
+
+
+
+
+ -4
+ 9
+ SN3D
+
+
+
+
+ -3
+ 9
+ SN3D
+
+
+
+
+ -2
+ 9
+ SN3D
+
+
+
+
+ -1
+ 9
+ SN3D
+
+
+
+
+ 0
+ 9
+ SN3D
+
+
+
+
+ 1
+ 9
+ SN3D
+
+
+
+
+ 2
+ 9
+ SN3D
+
+
+
+
+ 3
+ 9
+ SN3D
+
+
+
+
+ 4
+ 9
+ SN3D
+
+
+
+
+ 5
+ 9
+ SN3D
+
+
+
+
+ 6
+ 9
+ SN3D
+
+
+
+
+ 7
+ 9
+ SN3D
+
+
+
+
+ 8
+ 9
+ SN3D
+
+
+
+
+ 9
+ 9
+ SN3D
+
+
+
+
+ -10
+ 10
+ SN3D
+
+
+
+
+ -9
+ 10
+ SN3D
+
+
+
+
+ -8
+ 10
+ SN3D
+
+
+
+
+ -7
+ 10
+ SN3D
+
+
+
+
+ -6
+ 10
+ SN3D
+
+
+
+
+ -5
+ 10
+ SN3D
+
+
+
+
+ -4
+ 10
+ SN3D
+
+
+
+
+ -3
+ 10
+ SN3D
+
+
+
+
+ -2
+ 10
+ SN3D
+
+
+
+
+ -1
+ 10
+ SN3D
+
+
+
+
+ 0
+ 10
+ SN3D
+
+
+
+
+ 1
+ 10
+ SN3D
+
+
+
+
+ 2
+ 10
+ SN3D
+
+
+
+
+ 3
+ 10
+ SN3D
+
+
+
+
+ 4
+ 10
+ SN3D
+
+
+
+
+ 5
+ 10
+ SN3D
+
+
+
+
+ 6
+ 10
+ SN3D
+
+
+
+
+ 7
+ 10
+ SN3D
+
+
+
+
+ 8
+ 10
+ SN3D
+
+
+
+
+ 9
+ 10
+ SN3D
+
+
+
+
+ 10
+ 10
+ SN3D
+
+
+
+
+ 0
+ 0
+ N3D
+
+
+
+
+ -1
+ 1
+ N3D
+
+
+
+
+ 0
+ 1
+ N3D
+
+
+
+
+ 1
+ 1
+ N3D
+
+
+
+
+ -2
+ 2
+ N3D
+
+
+
+
+ -1
+ 2
+ N3D
+
+
+
+
+ 0
+ 2
+ N3D
+
+
+
+
+ 1
+ 2
+ N3D
+
+
+
+
+ 2
+ 2
+ N3D
+
+
+
+
+ -3
+ 3
+ N3D
+
+
+
+
+ -2
+ 3
+ N3D
+
+
+
+
+ -1
+ 3
+ N3D
+
+
+
+
+ 0
+ 3
+ N3D
+
+
+
+
+ 1
+ 3
+ N3D
+
+
+
+
+ 2
+ 3
+ N3D
+
+
+
+
+ 3
+ 3
+ N3D
+
+
+
+
+ -4
+ 4
+ N3D
+
+
+
+
+ -3
+ 4
+ N3D
+
+
+
+
+ -2
+ 4
+ N3D
+
+
+
+
+ -1
+ 4
+ N3D
+
+
+
+
+ 0
+ 4
+ N3D
+
+
+
+
+ 1
+ 4
+ N3D
+
+
+
+
+ 2
+ 4
+ N3D
+
+
+
+
+ 3
+ 4
+ N3D
+
+
+
+
+ 4
+ 4
+ N3D
+
+
+
+
+ -5
+ 5
+ N3D
+
+
+
+
+ -4
+ 5
+ N3D
+
+
+
+
+ -3
+ 5
+ N3D
+
+
+
+
+ -2
+ 5
+ N3D
+
+
+
+
+ -1
+ 5
+ N3D
+
+
+
+
+ 0
+ 5
+ N3D
+
+
+
+
+ 1
+ 5
+ N3D
+
+
+
+
+ 2
+ 5
+ N3D
+
+
+
+
+ 3
+ 5
+ N3D
+
+
+
+
+ 4
+ 5
+ N3D
+
+
+
+
+ 5
+ 5
+ N3D
+
+
+
+
+ -6
+ 6
+ N3D
+
+
+
+
+ -5
+ 6
+ N3D
+
+
+
+
+ -4
+ 6
+ N3D
+
+
+
+
+ -3
+ 6
+ N3D
+
+
+
+
+ -2
+ 6
+ N3D
+
+
+
+
+ -1
+ 6
+ N3D
+
+
+
+
+ 0
+ 6
+ N3D
+
+
+
+
+ 1
+ 6
+ N3D
+
+
+
+
+ 2
+ 6
+ N3D
+
+
+
+
+ 3
+ 6
+ N3D
+
+
+
+
+ 4
+ 6
+ N3D
+
+
+
+
+ 5
+ 6
+ N3D
+
+
+
+
+ 6
+ 6
+ N3D
+
+
+
+
+ -7
+ 7
+ N3D
+
+
+
+
+ -6
+ 7
+ N3D
+
+
+
+
+ -5
+ 7
+ N3D
+
+
+
+
+ -4
+ 7
+ N3D
+
+
+
+
+ -3
+ 7
+ N3D
+
+
+
+
+ -2
+ 7
+ N3D
+
+
+
+
+ -1
+ 7
+ N3D
+
+
+
+
+ 0
+ 7
+ N3D
+
+
+
+
+ 1
+ 7
+ N3D
+
+
+
+
+ 2
+ 7
+ N3D
+
+
+
+
+ 3
+ 7
+ N3D
+
+
+
+
+ 4
+ 7
+ N3D
+
+
+
+
+ 5
+ 7
+ N3D
+
+
+
+
+ 6
+ 7
+ N3D
+
+
+
+
+ 7
+ 7
+ N3D
+
+
+
+
+ -8
+ 8
+ N3D
+
+
+
+
+ -7
+ 8
+ N3D
+
+
+
+
+ -6
+ 8
+ N3D
+
+
+
+
+ -5
+ 8
+ N3D
+
+
+
+
+ -4
+ 8
+ N3D
+
+
+
+
+ -3
+ 8
+ N3D
+
+
+
+
+ -2
+ 8
+ N3D
+
+
+
+
+ -1
+ 8
+ N3D
+
+
+
+
+ 0
+ 8
+ N3D
+
+
+
+
+ 1
+ 8
+ N3D
+
+
+
+
+ 2
+ 8
+ N3D
+
+
+
+
+ 3
+ 8
+ N3D
+
+
+
+
+ 4
+ 8
+ N3D
+
+
+
+
+ 5
+ 8
+ N3D
+
+
+
+
+ 6
+ 8
+ N3D
+
+
+
+
+ 7
+ 8
+ N3D
+
+
+
+
+ 8
+ 8
+ N3D
+
+
+
+
+ -9
+ 9
+ N3D
+
+
+
+
+ -8
+ 9
+ N3D
+
+
+
+
+ -7
+ 9
+ N3D
+
+
+
+
+ -6
+ 9
+ N3D
+
+
+
+
+ -5
+ 9
+ N3D
+
+
+
+
+ -4
+ 9
+ N3D
+
+
+
+
+ -3
+ 9
+ N3D
+
+
+
+
+ -2
+ 9
+ N3D
+
+
+
+
+ -1
+ 9
+ N3D
+
+
+
+
+ 0
+ 9
+ N3D
+
+
+
+
+ 1
+ 9
+ N3D
+
+
+
+
+ 2
+ 9
+ N3D
+
+
+
+
+ 3
+ 9
+ N3D
+
+
+
+
+ 4
+ 9
+ N3D
+
+
+
+
+ 5
+ 9
+ N3D
+
+
+
+
+ 6
+ 9
+ N3D
+
+
+
+
+ 7
+ 9
+ N3D
+
+
+
+
+ 8
+ 9
+ N3D
+
+
+
+
+ 9
+ 9
+ N3D
+
+
+
+
+ -10
+ 10
+ N3D
+
+
+
+
+ -9
+ 10
+ N3D
+
+
+
+
+ -8
+ 10
+ N3D
+
+
+
+
+ -7
+ 10
+ N3D
+
+
+
+
+ -6
+ 10
+ N3D
+
+
+
+
+ -5
+ 10
+ N3D
+
+
+
+
+ -4
+ 10
+ N3D
+
+
+
+
+ -3
+ 10
+ N3D
+
+
+
+
+ -2
+ 10
+ N3D
+
+
+
+
+ -1
+ 10
+ N3D
+
+
+
+
+ 0
+ 10
+ N3D
+
+
+
+
+ 1
+ 10
+ N3D
+
+
+
+
+ 2
+ 10
+ N3D
+
+
+
+
+ 3
+ 10
+ N3D
+
+
+
+
+ 4
+ 10
+ N3D
+
+
+
+
+ 5
+ 10
+ N3D
+
+
+
+
+ 6
+ 10
+ N3D
+
+
+
+
+ 7
+ 10
+ N3D
+
+
+
+
+ 8
+ 10
+ N3D
+
+
+
+
+ 9
+ 10
+ N3D
+
+
+
+
+ 10
+ 10
+ N3D
+
+
+
+
+ 0
+ 0
+ FuMa
+
+
+
+
+ -1
+ 1
+ FuMa
+
+
+
+
+ 0
+ 1
+ FuMa
+
+
+
+
+ 1
+ 1
+ FuMa
+
+
+
+
+ -2
+ 2
+ FuMa
+
+
+
+
+ -1
+ 2
+ FuMa
+
+
+
+
+ 0
+ 2
+ FuMa
+
+
+
+
+ 1
+ 2
+ FuMa
+
+
+
+
+ 2
+ 2
+ FuMa
+
+
+
+
+ -3
+ 3
+ FuMa
+
+
+
+
+ -2
+ 3
+ FuMa
+
+
+
+
+ -1
+ 3
+ FuMa
+
+
+
+
+ 0
+ 3
+ FuMa
+
+
+
+
+ 1
+ 3
+ FuMa
+
+
+
+
+ 2
+ 3
+ FuMa
+
+
+
+
+ 3
+ 3
+ FuMa
+
+
+
+ AC_00010001
+ AT_00010001_01
+
+
+ AC_00010002
+ AT_00010002_01
+
+
+ AC_00010003
+ AT_00010003_01
+
+
+ AC_00010004
+ AT_00010004_01
+
+
+ AC_00010005
+ AT_00010005_01
+
+
+ AC_00010006
+ AT_00010006_01
+
+
+ AC_00010007
+ AT_00010007_01
+
+
+ AC_00010008
+ AT_00010008_01
+
+
+ AC_00010009
+ AT_00010009_01
+
+
+ AC_0001000a
+ AT_0001000a_01
+
+
+ AC_0001000b
+ AT_0001000b_01
+
+
+ AC_0001000c
+ AT_0001000c_01
+
+
+ AC_0001000d
+ AT_0001000d_01
+
+
+ AC_0001000e
+ AT_0001000e_01
+
+
+ AC_0001000f
+ AT_0001000f_01
+
+
+ AC_00010010
+ AT_00010010_01
+
+
+ AC_00010011
+ AT_00010011_01
+
+
+ AC_00010012
+ AT_00010012_01
+
+
+ AC_00010013
+ AT_00010013_01
+
+
+ AC_00010014
+ AT_00010014_01
+
+
+ AC_00010015
+ AT_00010015_01
+
+
+ AC_00010016
+ AT_00010016_01
+
+
+ AC_00010017
+ AT_00010017_01
+
+
+ AC_00010018
+ AT_00010018_01
+
+
+ AC_00010019
+ AT_00010019_01
+
+
+ AC_0001001a
+ AT_0001001a_01
+
+
+ AC_0001001b
+ AT_0001001b_01
+
+
+ AC_0001001c
+ AT_0001001c_01
+
+
+ AC_0001001d
+ AT_0001001d_01
+
+
+ AC_0001001e
+ AT_0001001e_01
+
+
+ AC_0001001f
+ AT_0001001f_01
+
+
+ AC_00010020
+ AT_00010020_01
+
+
+ AC_00010021
+ AT_00010021_01
+
+
+ AC_00010022
+ AT_00010022_01
+
+
+ AC_00010023
+ AT_00010023_01
+
+
+ AC_00010024
+ AT_00010024_01
+
+
+ AC_00010025
+ AT_00010025_01
+
+
+ AC_00010026
+ AT_00010026_01
+
+
+ AC_00010027
+ AT_00010027_01
+
+
+ AC_00010028
+ AT_00010028_01
+
+
+ AC_00050001
+ AT_00050001_01
+
+
+ AC_00050002
+ AT_00050002_01
+
+
+ AC_00040001
+ AT_00040001_01
+
+
+ AC_00040002
+ AT_00040002_01
+
+
+ AC_00040003
+ AT_00040003_01
+
+
+ AC_00040004
+ AT_00040004_01
+
+
+ AC_00040005
+ AT_00040005_01
+
+
+ AC_00040006
+ AT_00040006_01
+
+
+ AC_00040007
+ AT_00040007_01
+
+
+ AC_00040008
+ AT_00040008_01
+
+
+ AC_00040009
+ AT_00040009_01
+
+
+ AC_0004000a
+ AT_0004000a_01
+
+
+ AC_0004000b
+ AT_0004000b_01
+
+
+ AC_0004000c
+ AT_0004000c_01
+
+
+ AC_0004000d
+ AT_0004000d_01
+
+
+ AC_0004000e
+ AT_0004000e_01
+
+
+ AC_0004000f
+ AT_0004000f_01
+
+
+ AC_00040010
+ AT_00040010_01
+
+
+ AC_00040011
+ AT_00040011_01
+
+
+ AC_00040012
+ AT_00040012_01
+
+
+ AC_00040013
+ AT_00040013_01
+
+
+ AC_00040014
+ AT_00040014_01
+
+
+ AC_00040015
+ AT_00040015_01
+
+
+ AC_00040016
+ AT_00040016_01
+
+
+ AC_00040017
+ AT_00040017_01
+
+
+ AC_00040018
+ AT_00040018_01
+
+
+ AC_00040019
+ AT_00040019_01
+
+
+ AC_0004001a
+ AT_0004001a_01
+
+
+ AC_0004001b
+ AT_0004001b_01
+
+
+ AC_0004001c
+ AT_0004001c_01
+
+
+ AC_0004001d
+ AT_0004001d_01
+
+
+ AC_0004001e
+ AT_0004001e_01
+
+
+ AC_0004001f
+ AT_0004001f_01
+
+
+ AC_00040020
+ AT_00040020_01
+
+
+ AC_00040021
+ AT_00040021_01
+
+
+ AC_00040022
+ AT_00040022_01
+
+
+ AC_00040023
+ AT_00040023_01
+
+
+ AC_00040024
+ AT_00040024_01
+
+
+ AC_00040025
+ AT_00040025_01
+
+
+ AC_00040026
+ AT_00040026_01
+
+
+ AC_00040027
+ AT_00040027_01
+
+
+ AC_00040028
+ AT_00040028_01
+
+
+ AC_00040029
+ AT_00040029_01
+
+
+ AC_0004002a
+ AT_0004002a_01
+
+
+ AC_0004002b
+ AT_0004002b_01
+
+
+ AC_0004002c
+ AT_0004002c_01
+
+
+ AC_0004002d
+ AT_0004002d_01
+
+
+ AC_0004002e
+ AT_0004002e_01
+
+
+ AC_0004002f
+ AT_0004002f_01
+
+
+ AC_00040030
+ AT_00040030_01
+
+
+ AC_00040031
+ AT_00040031_01
+
+
+ AC_00040032
+ AT_00040032_01
+
+
+ AC_00040033
+ AT_00040033_01
+
+
+ AC_00040034
+ AT_00040034_01
+
+
+ AC_00040035
+ AT_00040035_01
+
+
+ AC_00040036
+ AT_00040036_01
+
+
+ AC_00040037
+ AT_00040037_01
+
+
+ AC_00040038
+ AT_00040038_01
+
+
+ AC_00040039
+ AT_00040039_01
+
+
+ AC_0004003a
+ AT_0004003a_01
+
+
+ AC_0004003b
+ AT_0004003b_01
+
+
+ AC_0004003c
+ AT_0004003c_01
+
+
+ AC_0004003d
+ AT_0004003d_01
+
+
+ AC_0004003e
+ AT_0004003e_01
+
+
+ AC_0004003f
+ AT_0004003f_01
+
+
+ AC_00040040
+ AT_00040040_01
+
+
+ AC_00040041
+ AT_00040041_01
+
+
+ AC_00040042
+ AT_00040042_01
+
+
+ AC_00040043
+ AT_00040043_01
+
+
+ AC_00040044
+ AT_00040044_01
+
+
+ AC_00040045
+ AT_00040045_01
+
+
+ AC_00040046
+ AT_00040046_01
+
+
+ AC_00040047
+ AT_00040047_01
+
+
+ AC_00040048
+ AT_00040048_01
+
+
+ AC_00040049
+ AT_00040049_01
+
+
+ AC_0004004a
+ AT_0004004a_01
+
+
+ AC_0004004b
+ AT_0004004b_01
+
+
+ AC_0004004c
+ AT_0004004c_01
+
+
+ AC_0004004d
+ AT_0004004d_01
+
+
+ AC_0004004e
+ AT_0004004e_01
+
+
+ AC_0004004f
+ AT_0004004f_01
+
+
+ AC_00040050
+ AT_00040050_01
+
+
+ AC_00040051
+ AT_00040051_01
+
+
+ AC_00040052
+ AT_00040052_01
+
+
+ AC_00040053
+ AT_00040053_01
+
+
+ AC_00040054
+ AT_00040054_01
+
+
+ AC_00040055
+ AT_00040055_01
+
+
+ AC_00040056
+ AT_00040056_01
+
+
+ AC_00040057
+ AT_00040057_01
+
+
+ AC_00040058
+ AT_00040058_01
+
+
+ AC_00040059
+ AT_00040059_01
+
+
+ AC_0004005a
+ AT_0004005a_01
+
+
+ AC_0004005b
+ AT_0004005b_01
+
+
+ AC_0004005c
+ AT_0004005c_01
+
+
+ AC_0004005d
+ AT_0004005d_01
+
+
+ AC_0004005e
+ AT_0004005e_01
+
+
+ AC_0004005f
+ AT_0004005f_01
+
+
+ AC_00040060
+ AT_00040060_01
+
+
+ AC_00040061
+ AT_00040061_01
+
+
+ AC_00040062
+ AT_00040062_01
+
+
+ AC_00040063
+ AT_00040063_01
+
+
+ AC_00040064
+ AT_00040064_01
+
+
+ AC_00040065
+ AT_00040065_01
+
+
+ AC_00040066
+ AT_00040066_01
+
+
+ AC_00040067
+ AT_00040067_01
+
+
+ AC_00040068
+ AT_00040068_01
+
+
+ AC_00040069
+ AT_00040069_01
+
+
+ AC_0004006a
+ AT_0004006a_01
+
+
+ AC_0004006b
+ AT_0004006b_01
+
+
+ AC_0004006c
+ AT_0004006c_01
+
+
+ AC_0004006d
+ AT_0004006d_01
+
+
+ AC_0004006e
+ AT_0004006e_01
+
+
+ AC_0004006f
+ AT_0004006f_01
+
+
+ AC_00040070
+ AT_00040070_01
+
+
+ AC_00040071
+ AT_00040071_01
+
+
+ AC_00040072
+ AT_00040072_01
+
+
+ AC_00040073
+ AT_00040073_01
+
+
+ AC_00040074
+ AT_00040074_01
+
+
+ AC_00040075
+ AT_00040075_01
+
+
+ AC_00040076
+ AT_00040076_01
+
+
+ AC_00040077
+ AT_00040077_01
+
+
+ AC_00040078
+ AT_00040078_01
+
+
+ AC_00040079
+ AT_00040079_01
+
+
+ AC_00040101
+ AT_00040101_01
+
+
+ AC_00040102
+ AT_00040102_01
+
+
+ AC_00040103
+ AT_00040103_01
+
+
+ AC_00040104
+ AT_00040104_01
+
+
+ AC_00040105
+ AT_00040105_01
+
+
+ AC_00040106
+ AT_00040106_01
+
+
+ AC_00040107
+ AT_00040107_01
+
+
+ AC_00040108
+ AT_00040108_01
+
+
+ AC_00040109
+ AT_00040109_01
+
+
+ AC_0004010a
+ AT_0004010a_01
+
+
+ AC_0004010b
+ AT_0004010b_01
+
+
+ AC_0004010c
+ AT_0004010c_01
+
+
+ AC_0004010d
+ AT_0004010d_01
+
+
+ AC_0004010e
+ AT_0004010e_01
+
+
+ AC_0004010f
+ AT_0004010f_01
+
+
+ AC_00040110
+ AT_00040110_01
+
+
+ AC_00040111
+ AT_00040111_01
+
+
+ AC_00040112
+ AT_00040112_01
+
+
+ AC_00040113
+ AT_00040113_01
+
+
+ AC_00040114
+ AT_00040114_01
+
+
+ AC_00040115
+ AT_00040115_01
+
+
+ AC_00040116
+ AT_00040116_01
+
+
+ AC_00040117
+ AT_00040117_01
+
+
+ AC_00040118
+ AT_00040118_01
+
+
+ AC_00040119
+ AT_00040119_01
+
+
+ AC_0004011a
+ AT_0004011a_01
+
+
+ AC_0004011b
+ AT_0004011b_01
+
+
+ AC_0004011c
+ AT_0004011c_01
+
+
+ AC_0004011d
+ AT_0004011d_01
+
+
+ AC_0004011e
+ AT_0004011e_01
+
+
+ AC_0004011f
+ AT_0004011f_01
+
+
+ AC_00040120
+ AT_00040120_01
+
+
+ AC_00040121
+ AT_00040121_01
+
+
+ AC_00040122
+ AT_00040122_01
+
+
+ AC_00040123
+ AT_00040123_01
+
+
+ AC_00040124
+ AT_00040124_01
+
+
+ AC_00040125
+ AT_00040125_01
+
+
+ AC_00040126
+ AT_00040126_01
+
+
+ AC_00040127
+ AT_00040127_01
+
+
+ AC_00040128
+ AT_00040128_01
+
+
+ AC_00040129
+ AT_00040129_01
+
+
+ AC_0004012a
+ AT_0004012a_01
+
+
+ AC_0004012b
+ AT_0004012b_01
+
+
+ AC_0004012c
+ AT_0004012c_01
+
+
+ AC_0004012d
+ AT_0004012d_01
+
+
+ AC_0004012e
+ AT_0004012e_01
+
+
+ AC_0004012f
+ AT_0004012f_01
+
+
+ AC_00040130
+ AT_00040130_01
+
+
+ AC_00040131
+ AT_00040131_01
+
+
+ AC_00040132
+ AT_00040132_01
+
+
+ AC_00040133
+ AT_00040133_01
+
+
+ AC_00040134
+ AT_00040134_01
+
+
+ AC_00040135
+ AT_00040135_01
+
+
+ AC_00040136
+ AT_00040136_01
+
+
+ AC_00040137
+ AT_00040137_01
+
+
+ AC_00040138
+ AT_00040138_01
+
+
+ AC_00040139
+ AT_00040139_01
+
+
+ AC_0004013a
+ AT_0004013a_01
+
+
+ AC_0004013b
+ AT_0004013b_01
+
+
+ AC_0004013c
+ AT_0004013c_01
+
+
+ AC_0004013d
+ AT_0004013d_01
+
+
+ AC_0004013e
+ AT_0004013e_01
+
+
+ AC_0004013f
+ AT_0004013f_01
+
+
+ AC_00040140
+ AT_00040140_01
+
+
+ AC_00040141
+ AT_00040141_01
+
+
+ AC_00040142
+ AT_00040142_01
+
+
+ AC_00040143
+ AT_00040143_01
+
+
+ AC_00040144
+ AT_00040144_01
+
+
+ AC_00040145
+ AT_00040145_01
+
+
+ AC_00040146
+ AT_00040146_01
+
+
+ AC_00040147
+ AT_00040147_01
+
+
+ AC_00040148
+ AT_00040148_01
+
+
+ AC_00040149
+ AT_00040149_01
+
+
+ AC_0004014a
+ AT_0004014a_01
+
+
+ AC_0004014b
+ AT_0004014b_01
+
+
+ AC_0004014c
+ AT_0004014c_01
+
+
+ AC_0004014d
+ AT_0004014d_01
+
+
+ AC_0004014e
+ AT_0004014e_01
+
+
+ AC_0004014f
+ AT_0004014f_01
+
+
+ AC_00040150
+ AT_00040150_01
+
+
+ AC_00040151
+ AT_00040151_01
+
+
+ AC_00040152
+ AT_00040152_01
+
+
+ AC_00040153
+ AT_00040153_01
+
+
+ AC_00040154
+ AT_00040154_01
+
+
+ AC_00040155
+ AT_00040155_01
+
+
+ AC_00040156
+ AT_00040156_01
+
+
+ AC_00040157
+ AT_00040157_01
+
+
+ AC_00040158
+ AT_00040158_01
+
+
+ AC_00040159
+ AT_00040159_01
+
+
+ AC_0004015a
+ AT_0004015a_01
+
+
+ AC_0004015b
+ AT_0004015b_01
+
+
+ AC_0004015c
+ AT_0004015c_01
+
+
+ AC_0004015d
+ AT_0004015d_01
+
+
+ AC_0004015e
+ AT_0004015e_01
+
+
+ AC_0004015f
+ AT_0004015f_01
+
+
+ AC_00040160
+ AT_00040160_01
+
+
+ AC_00040161
+ AT_00040161_01
+
+
+ AC_00040162
+ AT_00040162_01
+
+
+ AC_00040163
+ AT_00040163_01
+
+
+ AC_00040164
+ AT_00040164_01
+
+
+ AC_00040165
+ AT_00040165_01
+
+
+ AC_00040166
+ AT_00040166_01
+
+
+ AC_00040167
+ AT_00040167_01
+
+
+ AC_00040168
+ AT_00040168_01
+
+
+ AC_00040169
+ AT_00040169_01
+
+
+ AC_0004016a
+ AT_0004016a_01
+
+
+ AC_0004016b
+ AT_0004016b_01
+
+
+ AC_0004016c
+ AT_0004016c_01
+
+
+ AC_0004016d
+ AT_0004016d_01
+
+
+ AC_0004016e
+ AT_0004016e_01
+
+
+ AC_0004016f
+ AT_0004016f_01
+
+
+ AC_00040170
+ AT_00040170_01
+
+
+ AC_00040171
+ AT_00040171_01
+
+
+ AC_00040172
+ AT_00040172_01
+
+
+ AC_00040173
+ AT_00040173_01
+
+
+ AC_00040174
+ AT_00040174_01
+
+
+ AC_00040175
+ AT_00040175_01
+
+
+ AC_00040176
+ AT_00040176_01
+
+
+ AC_00040177
+ AT_00040177_01
+
+
+ AC_00040178
+ AT_00040178_01
+
+
+ AC_00040179
+ AT_00040179_01
+
+
+ AC_00040201
+ AT_00040201_01
+
+
+ AC_00040202
+ AT_00040202_01
+
+
+ AC_00040203
+ AT_00040203_01
+
+
+ AC_00040204
+ AT_00040204_01
+
+
+ AC_00040205
+ AT_00040205_01
+
+
+ AC_00040206
+ AT_00040206_01
+
+
+ AC_00040207
+ AT_00040207_01
+
+
+ AC_00040208
+ AT_00040208_01
+
+
+ AC_00040209
+ AT_00040209_01
+
+
+ AC_0004020a
+ AT_0004020a_01
+
+
+ AC_0004020b
+ AT_0004020b_01
+
+
+ AC_0004020c
+ AT_0004020c_01
+
+
+ AC_0004020d
+ AT_0004020d_01
+
+
+ AC_0004020e
+ AT_0004020e_01
+
+
+ AC_0004020f
+ AT_0004020f_01
+
+
+ AC_00040210
+ AT_00040210_01
+
+
+ AS_00010001
+
+
+ AS_00010002
+
+
+ AS_00010003
+
+
+ AS_00010004
+
+
+ AS_00010005
+
+
+ AS_00010006
+
+
+ AS_00010007
+
+
+ AS_00010008
+
+
+ AS_00010009
+
+
+ AS_0001000a
+
+
+ AS_0001000b
+
+
+ AS_0001000c
+
+
+ AS_0001000d
+
+
+ AS_0001000e
+
+
+ AS_0001000f
+
+
+ AS_00010010
+
+
+ AS_00010011
+
+
+ AS_00010012
+
+
+ AS_00010013
+
+
+ AS_00010014
+
+
+ AS_00010015
+
+
+ AS_00010016
+
+
+ AS_00010017
+
+
+ AS_00010018
+
+
+ AS_00010019
+
+
+ AS_0001001a
+
+
+ AS_0001001b
+
+
+ AS_0001001c
+
+
+ AS_0001001d
+
+
+ AS_0001001e
+
+
+ AS_0001001f
+
+
+ AS_00010020
+
+
+ AS_00010021
+
+
+ AS_00010022
+
+
+ AS_00010023
+
+
+ AS_00010024
+
+
+ AS_00010025
+
+
+ AS_00010026
+
+
+ AS_00010027
+
+
+ AS_00010028
+
+
+ AS_00050001
+
+
+ AS_00050002
+
+
+ AS_00040001
+
+
+ AS_00040002
+
+
+ AS_00040003
+
+
+ AS_00040004
+
+
+ AS_00040005
+
+
+ AS_00040006
+
+
+ AS_00040007
+
+
+ AS_00040008
+
+
+ AS_00040009
+
+
+ AS_0004000a
+
+
+ AS_0004000b
+
+
+ AS_0004000c
+
+
+ AS_0004000d
+
+
+ AS_0004000e
+
+
+ AS_0004000f
+
+
+ AS_00040010
+
+
+ AS_00040011
+
+
+ AS_00040012
+
+
+ AS_00040013
+
+
+ AS_00040014
+
+
+ AS_00040015
+
+
+ AS_00040016
+
+
+ AS_00040017
+
+
+ AS_00040018
+
+
+ AS_00040019
+
+
+ AS_0004001a
+
+
+ AS_0004001b
+
+
+ AS_0004001c
+
+
+ AS_0004001d
+
+
+ AS_0004001e
+
+
+ AS_0004001f
+
+
+ AS_00040020
+
+
+ AS_00040021
+
+
+ AS_00040022
+
+
+ AS_00040023
+
+
+ AS_00040024
+
+
+ AS_00040025
+
+
+ AS_00040026
+
+
+ AS_00040027
+
+
+ AS_00040028
+
+
+ AS_00040029
+
+
+ AS_0004002a
+
+
+ AS_0004002b
+
+
+ AS_0004002c
+
+
+ AS_0004002d
+
+
+ AS_0004002e
+
+
+ AS_0004002f
+
+
+ AS_00040030
+
+
+ AS_00040031
+
+
+ AS_00040032
+
+
+ AS_00040033
+
+
+ AS_00040034
+
+
+ AS_00040035
+
+
+ AS_00040036
+
+
+ AS_00040037
+
+
+ AS_00040038
+
+
+ AS_00040039
+
+
+ AS_0004003a
+
+
+ AS_0004003b
+
+
+ AS_0004003c
+
+
+ AS_0004003d
+
+
+ AS_0004003e
+
+
+ AS_0004003f
+
+
+ AS_00040040
+
+
+ AS_00040041
+
+
+ AS_00040042
+
+
+ AS_00040043
+
+
+ AS_00040044
+
+
+ AS_00040045
+
+
+ AS_00040046
+
+
+ AS_00040047
+
+
+ AS_00040048
+
+
+ AS_00040049
+
+
+ AS_0004004a
+
+
+ AS_0004004b
+
+
+ AS_0004004c
+
+
+ AS_0004004d
+
+
+ AS_0004004e
+
+
+ AS_0004004f
+
+
+ AS_00040050
+
+
+ AS_00040051
+
+
+ AS_00040052
+
+
+ AS_00040053
+
+
+ AS_00040054
+
+
+ AS_00040055
+
+
+ AS_00040056
+
+
+ AS_00040057
+
+
+ AS_00040058
+
+
+ AS_00040059
+
+
+ AS_0004005a
+
+
+ AS_0004005b
+
+
+ AS_0004005c
+
+
+ AS_0004005d
+
+
+ AS_0004005e
+
+
+ AS_0004005f
+
+
+ AS_00040060
+
+
+ AS_00040061
+
+
+ AS_00040062
+
+
+ AS_00040063
+
+
+ AS_00040064
+
+
+ AS_00040065
+
+
+ AS_00040066
+
+
+ AS_00040067
+
+
+ AS_00040068
+
+
+ AS_00040069
+
+
+ AS_0004006a
+
+
+ AS_0004006b
+
+
+ AS_0004006c
+
+
+ AS_0004006d
+
+
+ AS_0004006e
+
+
+ AS_0004006f
+
+
+ AS_00040070
+
+
+ AS_00040071
+
+
+ AS_00040072
+
+
+ AS_00040073
+
+
+ AS_00040074
+
+
+ AS_00040075
+
+
+ AS_00040076
+
+
+ AS_00040077
+
+
+ AS_00040078
+
+
+ AS_00040079
+
+
+ AS_00040101
+
+
+ AS_00040102
+
+
+ AS_00040103
+
+
+ AS_00040104
+
+
+ AS_00040105
+
+
+ AS_00040106
+
+
+ AS_00040107
+
+
+ AS_00040108
+
+
+ AS_00040109
+
+
+ AS_0004010a
+
+
+ AS_0004010b
+
+
+ AS_0004010c
+
+
+ AS_0004010d
+
+
+ AS_0004010e
+
+
+ AS_0004010f
+
+
+ AS_00040110
+
+
+ AS_00040111
+
+
+ AS_00040112
+
+
+ AS_00040113
+
+
+ AS_00040114
+
+
+ AS_00040115
+
+
+ AS_00040116
+
+
+ AS_00040117
+
+
+ AS_00040118
+
+
+ AS_00040119
+
+
+ AS_0004011a
+
+
+ AS_0004011b
+
+
+ AS_0004011c
+
+
+ AS_0004011d
+
+
+ AS_0004011e
+
+
+ AS_0004011f
+
+
+ AS_00040120
+
+
+ AS_00040121
+
+
+ AS_00040122
+
+
+ AS_00040123
+
+
+ AS_00040124
+
+
+ AS_00040125
+
+
+ AS_00040126
+
+
+ AS_00040127
+
+
+ AS_00040128
+
+
+ AS_00040129
+
+
+ AS_0004012a
+
+
+ AS_0004012b
+
+
+ AS_0004012c
+
+
+ AS_0004012d
+
+
+ AS_0004012e
+
+
+ AS_0004012f
+
+
+ AS_00040130
+
+
+ AS_00040131
+
+
+ AS_00040132
+
+
+ AS_00040133
+
+
+ AS_00040134
+
+
+ AS_00040135
+
+
+ AS_00040136
+
+
+ AS_00040137
+
+
+ AS_00040138
+
+
+ AS_00040139
+
+
+ AS_0004013a
+
+
+ AS_0004013b
+
+
+ AS_0004013c
+
+
+ AS_0004013d
+
+
+ AS_0004013e
+
+
+ AS_0004013f
+
+
+ AS_00040140
+
+
+ AS_00040141
+
+
+ AS_00040142
+
+
+ AS_00040143
+
+
+ AS_00040144
+
+
+ AS_00040145
+
+
+ AS_00040146
+
+
+ AS_00040147
+
+
+ AS_00040148
+
+
+ AS_00040149
+
+
+ AS_0004014a
+
+
+ AS_0004014b
+
+
+ AS_0004014c
+
+
+ AS_0004014d
+
+
+ AS_0004014e
+
+
+ AS_0004014f
+
+
+ AS_00040150
+
+
+ AS_00040151
+
+
+ AS_00040152
+
+
+ AS_00040153
+
+
+ AS_00040154
+
+
+ AS_00040155
+
+
+ AS_00040156
+
+
+ AS_00040157
+
+
+ AS_00040158
+
+
+ AS_00040159
+
+
+ AS_0004015a
+
+
+ AS_0004015b
+
+
+ AS_0004015c
+
+
+ AS_0004015d
+
+
+ AS_0004015e
+
+
+ AS_0004015f
+
+
+ AS_00040160
+
+
+ AS_00040161
+
+
+ AS_00040162
+
+
+ AS_00040163
+
+
+ AS_00040164
+
+
+ AS_00040165
+
+
+ AS_00040166
+
+
+ AS_00040167
+
+
+ AS_00040168
+
+
+ AS_00040169
+
+
+ AS_0004016a
+
+
+ AS_0004016b
+
+
+ AS_0004016c
+
+
+ AS_0004016d
+
+
+ AS_0004016e
+
+
+ AS_0004016f
+
+
+ AS_00040170
+
+
+ AS_00040171
+
+
+ AS_00040172
+
+
+ AS_00040173
+
+
+ AS_00040174
+
+
+ AS_00040175
+
+
+ AS_00040176
+
+
+ AS_00040177
+
+
+ AS_00040178
+
+
+ AS_00040179
+
+
+ AS_00040201
+
+
+ AS_00040202
+
+
+ AS_00040203
+
+
+ AS_00040204
+
+
+ AS_00040205
+
+
+ AS_00040206
+
+
+ AS_00040207
+
+
+ AS_00040208
+
+
+ AS_00040209
+
+
+ AS_0004020a
+
+
+ AS_0004020b
+
+
+ AS_0004020c
+
+
+ AS_0004020d
+
+
+ AS_0004020e
+
+
+ AS_0004020f
+
+
+ AS_00040210
+
+
+
+
+
diff --git a/ear/fileio/adm/elements/__init__.py b/ear/fileio/adm/elements/__init__.py
new file mode 100644
index 00000000..9dcf51f6
--- /dev/null
+++ b/ear/fileio/adm/elements/__init__.py
@@ -0,0 +1,9 @@
+# flake8: noqa
+from .main_elements import (AudioChannelFormat, AudioPackFormat, AudioTrackFormat,
+ AudioStreamFormat, AudioProgramme, AudioContent, AudioObject, AudioTrackUID,
+ FormatDefinition, TypeDefinition, Frequency)
+from .block_formats import (AudioBlockFormatObjects, ChannelLock, ObjectDivergence,
+ JumpPosition, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural, AudioBlockFormatHoa,
+ CartesianZone, PolarZone)
+from .geom import (DirectSpeakerPolarPosition, DirectSpeakerCartesianPosition, BoundCoordinate,
+ ObjectPolarPosition, ObjectCartesianPosition, ScreenEdgeLock)
diff --git a/ear/fileio/adm/elements/block_formats.py b/ear/fileio/adm/elements/block_formats.py
new file mode 100644
index 00000000..d9c53786
--- /dev/null
+++ b/ear/fileio/adm/elements/block_formats.py
@@ -0,0 +1,91 @@
+from attr import attrs, attrib, Factory
+from attr.validators import instance_of, optional
+from fractions import Fraction
+from ....common import list_of
+from .geom import convert_object_position, DirectSpeakerPosition, ObjectPosition
+
+
+@attrs(slots=True)
+class BlockFormat(object):
+ id = attrib(default=None)
+ rtime = attrib(validator=optional(instance_of(Fraction)), default=None)
+ duration = attrib(validator=optional(instance_of(Fraction)), default=None)
+
+
+@attrs(slots=True)
+class AudioBlockFormatMatrix(BlockFormat):
+ pass
+
+
+@attrs(slots=True)
+class ChannelLock(object):
+ maxDistance = attrib(default=None, validator=optional(instance_of(float)))
+
+
+@attrs(slots=True)
+class ObjectDivergence(object):
+ value = attrib(validator=instance_of(float))
+ azimuthRange = attrib(default=None, validator=optional(instance_of(float)))
+ positionRange = attrib(default=None, validator=optional(instance_of(float)))
+
+
+@attrs(slots=True)
+class JumpPosition(object):
+ flag = attrib(default=False, validator=instance_of(bool))
+ interpolationLength = attrib(default=None, validator=optional(instance_of(Fraction)))
+
+
+@attrs(slots=True)
+class CartesianZone(object):
+ minX = attrib(validator=instance_of(float))
+ minY = attrib(validator=instance_of(float))
+ minZ = attrib(validator=instance_of(float))
+ maxX = attrib(validator=instance_of(float))
+ maxY = attrib(validator=instance_of(float))
+ maxZ = attrib(validator=instance_of(float))
+
+
+@attrs(slots=True)
+class PolarZone(object):
+ minElevation = attrib(validator=instance_of(float))
+ maxElevation = attrib(validator=instance_of(float))
+ minAzimuth = attrib(validator=instance_of(float))
+ maxAzimuth = attrib(validator=instance_of(float))
+
+
+@attrs(slots=True)
+class AudioBlockFormatObjects(BlockFormat):
+ position = attrib(default=None, validator=instance_of(ObjectPosition), convert=convert_object_position)
+ cartesian = attrib(convert=bool, default=False)
+ width = attrib(convert=float, default=0.)
+ height = attrib(convert=float, default=0.)
+ depth = attrib(convert=float, default=0.)
+ gain = attrib(convert=float, default=1.)
+ diffuse = attrib(convert=float, default=0.)
+ channelLock = attrib(default=None, validator=optional(instance_of(ChannelLock)))
+ objectDivergence = attrib(default=None, validator=optional(instance_of(ObjectDivergence)))
+ jumpPosition = attrib(default=Factory(JumpPosition))
+ screenRef = attrib(convert=bool, default=False)
+ importance = attrib(default=10, validator=instance_of(int))
+ zoneExclusion = attrib(default=Factory(list), validator=list_of((CartesianZone, PolarZone)))
+
+
+@attrs(slots=True)
+class AudioBlockFormatDirectSpeakers(BlockFormat):
+ position = attrib(default=None, validator=instance_of(DirectSpeakerPosition))
+ speakerLabel = attrib(default=Factory(list))
+
+
+@attrs(slots=True)
+class AudioBlockFormatHoa(BlockFormat):
+ equation = attrib(default=None, validator=optional(instance_of(str)))
+ order = attrib(default=None, validator=optional(instance_of(int)))
+ degree = attrib(default=None, validator=optional(instance_of(int)))
+ normalization = attrib(default="SN3D", validator=instance_of(str))
+ nfcRefDist = attrib(default=None, validator=optional(instance_of(float)))
+ screenRef = attrib(default=False, validator=instance_of(bool))
+
+
+@attrs(slots=True)
+class AudioBlockFormatBinaural(BlockFormat):
+ pass
diff --git a/ear/fileio/adm/elements/geom.py b/ear/fileio/adm/elements/geom.py
new file mode 100644
index 00000000..53718bf5
--- /dev/null
+++ b/ear/fileio/adm/elements/geom.py
@@ -0,0 +1,122 @@
+from attr import attrs, attrib, Factory
+from attr.validators import instance_of, optional
+import collections
+from ....common import PolarPositionMixin, CartesianPositionMixin, PolarPosition, CartesianPosition, cart, validate_range
+
+
+def convert_object_position(value):
+ if isinstance(value, (ObjectPolarPosition, ObjectCartesianPosition)):
+ return value
+ elif isinstance(value, PolarPosition):
+ return ObjectPolarPosition.from_PolarPosition(value)
+ elif isinstance(value, CartesianPosition):
+ return ObjectCartesianPosition.from_CartesianPosition(value)
+ elif isinstance(value, collections.Mapping):
+ if 'azimuth' in value:
+ return ObjectPolarPosition(**value)
+ else:
+ return ObjectCartesianPosition(**value)
+ else:
+ raise TypeError("cannot convert {value!r} to ObjectPolarPosition or ObjectCartesianPosition".format(value=value))
+
+
+@attrs(slots=True)
+class ScreenEdgeLock(object):
+ horizontal = attrib(default=None)
+ vertical = attrib(default=None)
+
+
+class ObjectPosition(object):
+ """Base for classes representing data contained in `audioBlockFormat`
+ `position` elements for Objects."""
+ __slots__ = ()
+
+
+@attrs(slots=True)
+class ObjectPolarPosition(ObjectPosition, PolarPositionMixin):
+ """Represents data contained in `audioBlockFormat` `position` elements for
+ Objects where polar coordinates are used."""
+ azimuth = attrib(convert=float, validator=validate_range(-180, 180))
+ elevation = attrib(convert=float, validator=validate_range(-90, 90))
+ distance = attrib(convert=float, validator=validate_range(0, float('inf')),
+ default=1.0)
+ screenEdgeLock = attrib(default=Factory(ScreenEdgeLock), validator=instance_of(ScreenEdgeLock))
+
+ @classmethod
+ def from_PolarPosition(cls, position):
+ return cls(azimuth=position.azimuth, elevation=position.elevation, distance=position.distance)
+
+
+@attrs(slots=True)
+class ObjectCartesianPosition(ObjectPosition, CartesianPositionMixin):
+ """Represents data contained in `audioBlockFormat` `position` elements for
+ Objects where Cartesian coordinates are used."""
+ X = attrib(convert=float)
+ Y = attrib(convert=float)
+ Z = attrib(convert=float)
+ screenEdgeLock = attrib(default=Factory(ScreenEdgeLock), validator=instance_of(ScreenEdgeLock))
+
+ @classmethod
+ def from_CartesianPosition(cls, position):
+ return cls(X=position.X, Y=position.Y, Z=position.Z)
+
+
+@attrs(slots=True)
+class BoundCoordinate(object):
+ value = attrib(validator=instance_of(float))
+ min = attrib(validator=optional(instance_of(float)), default=None)
+ max = attrib(validator=optional(instance_of(float)), default=None)
+
+
+class DirectSpeakerPosition(object):
+ """Base for classes representing data contained in `audioBlockFormat`
+ `position` elements for DirestSpeakers."""
+ __slots__ = ()
+
+
+@attrs(slots=True)
+class DirectSpeakerPolarPosition(DirectSpeakerPosition, PolarPositionMixin):
+ """Represents data contained in `audioBlockFormat` `position` elements for
+ DirectSpeakers where polar coordinates are used."""
+ bounded_azimuth = attrib(validator=instance_of(BoundCoordinate))
+ bounded_elevation = attrib(validator=instance_of(BoundCoordinate))
+ bounded_distance = attrib(validator=instance_of(BoundCoordinate),
+ default=Factory(lambda: BoundCoordinate(1.)))
+ screenEdgeLock = attrib(default=Factory(ScreenEdgeLock), validator=instance_of(ScreenEdgeLock))
+
+ @property
+ def azimuth(self):
+ return self.bounded_azimuth.value
+
+ @property
+ def elevation(self):
+ return self.bounded_elevation.value
+
+ @property
+ def distance(self):
+ return self.bounded_distance.value
+
+ def as_cartesian_array(self):
+ return cart(self.azimuth, self.elevation, self.distance)
+
+
+@attrs(slots=True)
+class DirectSpeakerCartesianPosition(DirectSpeakerPosition, CartesianPositionMixin):
+ """Represents data contained in `audioBlockFormat` `position` elements for
+ DirectSpeakers where Cartesian coordinates are used."""
+ bounded_X = attrib(validator=instance_of(BoundCoordinate))
+ bounded_Y = attrib(validator=instance_of(BoundCoordinate))
+ bounded_Z = attrib(validator=instance_of(BoundCoordinate))
+ screenEdgeLock = attrib(default=Factory(ScreenEdgeLock), validator=instance_of(ScreenEdgeLock))
+
+ @property
+ def X(self):
+ return self.bounded_X.value
+
+ @property
+ def Y(self):
+ return self.bounded_Y.value
+
+ @property
+ def Z(self):
+ return self.bounded_Z.value
diff --git a/ear/fileio/adm/elements/main_elements.py b/ear/fileio/adm/elements/main_elements.py
new file mode 100644
index 00000000..52f0d25c
--- /dev/null
+++ b/ear/fileio/adm/elements/main_elements.py
@@ -0,0 +1,211 @@
+from attr import attrs, attrib, Factory
+from attr.validators import instance_of, optional
+from enum import Enum
+from fractions import Fraction
+from six import string_types
+
+from ....common import CartesianScreen, PolarScreen, default_screen
+
+
+class TypeDefinition(Enum):
+ DirectSpeakers = 1
+ Matrix = 2
+ Objects = 3
+ HOA = 4
+ Binaural = 5
+
+
+class FormatDefinition(Enum):
+ PCM = 1
+
+
+@attrs(slots=True)
+class ADMElement(object):
+ id = attrib(default=None)
+ adm_parent = attrib(default=None)
+ is_common_definition = attrib(default=False, validator=instance_of(bool))
+
+ @property
+ def element_type(self):
+ return type(self).__name__
+
+ def _lookup_elements(self, idRefs):
+ return [self.adm_parent.lookup_element(key) for key in idRefs]
+
+ def _lookup_element(self, idRef):
+ if idRef is None: return None
+ return self.adm_parent.lookup_element(idRef)
+
+
+@attrs(slots=True)
+class AudioProgramme(ADMElement):
+ audioProgrammeName = attrib(default=None, validator=instance_of(string_types))
+ audioProgrammeLanguage = attrib(default=None)
+ start = attrib(default=None)
+ end = attrib(default=None)
+ maxDuckingDepth = attrib(default=None)
+ audioContents = attrib(default=Factory(list), repr=False)
+
+ audioContentIDRef = attrib(default=Factory(list))
+
+ referenceScreen = attrib(validator=optional(instance_of((CartesianScreen, PolarScreen))),
+ default=default_screen)
+
+ def lazy_lookup_references(self):
+ if self.audioContentIDRef is not None:
+ self.audioContents = self._lookup_elements(self.audioContentIDRef)
+ self.audioContentIDRef = None
+
+
+@attrs(slots=True)
+class AudioContent(ADMElement):
+ audioContentName = attrib(default=None, validator=instance_of(string_types))
+ audioContentLanguage = attrib(default=None)
+ loudnessMetadata = attrib(default=None)
+ dialogue = attrib(default=None)
+ audioObjects = attrib(default=Factory(list), repr=False)
+
+ audioObjectIDRef = attrib(default=None)
+
+ def lazy_lookup_references(self):
+ if self.audioObjectIDRef is not None:
+ self.audioObjects = self._lookup_elements(self.audioObjectIDRef)
+ self.audioObjectIDRef = None
+
+
+@attrs(slots=True)
+class AudioObject(ADMElement):
+ audioObjectName = attrib(default=None, validator=instance_of(string_types))
+ start = attrib(validator=optional(instance_of(Fraction)), default=None)
+ duration = attrib(validator=optional(instance_of(Fraction)), default=None)
+ importance = attrib(default=None, validator=optional(instance_of(int)))
+ interact = attrib(default=None, validator=optional(instance_of(bool)))
+ disableDucking = attrib(default=None, validator=optional(instance_of(bool)))
+ dialogue = attrib(default=None, validator=optional(instance_of(int)))
+ audioPackFormats = attrib(default=Factory(list), repr=False)
+ audioTrackUIDs = attrib(default=Factory(list), repr=False)
+ audioObjects = attrib(default=Factory(list), repr=False)
+ audioComplementaryObjects = attrib(default=Factory(list), repr=False)
+
+ audioPackFormatIDRef = attrib(default=None)
+ audioTrackUIDRef = attrib(default=None)
+ audioObjectIDRef = attrib(default=None)
+ audioComplementaryObjectIDRef = attrib(default=None)
+
+ def lazy_lookup_references(self):
+ if self.audioPackFormatIDRef is not None:
+ self.audioPackFormats = self._lookup_elements(self.audioPackFormatIDRef)
+ self.audioPackFormatIDRef = None
+ if self.audioTrackUIDRef is not None:
+ self.audioTrackUIDs = self._lookup_elements(self.audioTrackUIDRef)
+ self.audioTrackUIDRef = None
+ if self.audioObjectIDRef is not None:
+ self.audioObjects = self._lookup_elements(self.audioObjectIDRef)
+ self.audioObjectIDRef = None
+ if self.audioComplementaryObjectIDRef is not None:
+ self.audioComplementaryObjects = self._lookup_elements(self.audioComplementaryObjectIDRef)
+ self.audioComplementaryObjectIDRef = None
+
+
+@attrs(slots=True)
+class AudioPackFormat(ADMElement):
+ audioPackFormatName = attrib(default=None, validator=instance_of(string_types))
+ type = attrib(default=None, validator=instance_of(TypeDefinition))
+ absoluteDistance = attrib(default=None)
+ audioChannelFormats = attrib(default=Factory(list), repr=False)
+ audioPackFormats = attrib(default=Factory(list), repr=False)
+ importance = attrib(default=None, validator=optional(instance_of(int)))
+
+ audioChannelFormatIDRef = attrib(default=None)
+ audioPackFormatIDRef = attrib(default=None)
+
+ def lazy_lookup_references(self):
+ if self.audioChannelFormatIDRef is not None:
+ self.audioChannelFormats = self._lookup_elements(self.audioChannelFormatIDRef)
+ self.audioChannelFormatIDRef = None
+ if self.audioPackFormatIDRef is not None:
+ self.audioPackFormats = self._lookup_elements(self.audioPackFormatIDRef)
+ self.audioPackFormatIDRef = None
+
+
+@attrs(slots=True)
+class Frequency(object):
+ lowPass = attrib(default=None, validator=optional(instance_of(float)))
+ highPass = attrib(default=None, validator=optional(instance_of(float)))
+
+
+@attrs(slots=True)
+class AudioChannelFormat(ADMElement):
+ audioChannelFormatName = attrib(default=None, validator=instance_of(string_types))
+ type = attrib(default=None, validator=instance_of(TypeDefinition))
+ audioBlockFormats = attrib(default=Factory(list))
+ frequency = attrib(default=Factory(Frequency), validator=instance_of(Frequency))
+
+ def lazy_lookup_references(self):
+ pass
+
+
+@attrs(slots=True)
+class AudioStreamFormat(ADMElement):
+ audioStreamFormatName = attrib(default=None, validator=instance_of(string_types))
+
+ format = attrib(default=None, validator=instance_of(FormatDefinition))
+
+ audioTrackFormats = attrib(default=Factory(list), repr=False)
+ audioChannelFormat = attrib(default=None, repr=False)
+ audioPackFormat = attrib(default=None, repr=False)
+
+ audioTrackFormatIDRef = attrib(default=None)
+ audioChannelFormatIDRef = attrib(default=None)
+ audioPackFormatIDRef = attrib(default=None)
+
+ def lazy_lookup_references(self):
+ if self.audioChannelFormatIDRef is not None:
+ self.audioChannelFormat = self._lookup_element(self.audioChannelFormatIDRef)
+ self.audioChannelFormatIDRef = None
+ if self.audioPackFormatIDRef is not None:
+ self.audioPackFormat = self._lookup_element(self.audioPackFormatIDRef)
+ self.audioPackFormatIDRef = None
+ if self.audioTrackFormatIDRef is not None:
+ self.audioTrackFormats = self._lookup_elements(self.audioTrackFormatIDRef)
+ self.audioTrackFormatIDRef = None
+
+
+@attrs(slots=True)
+class AudioTrackFormat(ADMElement):
+ audioTrackFormatName = attrib(default=None, validator=instance_of(string_types))
+ format = attrib(default=None, validator=instance_of(FormatDefinition))
+
+ audioStreamFormatIDRef = attrib(default=None)
+
+ def lazy_lookup_references(self):
+ # check that there is a reference from the referenced stream format
+ # back to ourselves
+ if self.audioStreamFormatIDRef is not None:
+ stream = self._lookup_element(self.audioStreamFormatIDRef)
+
+ # cannot use 'in', as we want to check identity, not equality
+ if not any(track_format is self for track_format in stream.audioTrackFormats):
+ raise Exception("track format {id} references stream format {ref} that does not reference it back.".format(
+ id=self.id, ref=self.audioStreamFormatIDRef))
+ self.audioStreamFormatIDRef = None
+
+
+@attrs(slots=True)
+class AudioTrackUID(ADMElement):
+ trackIndex = attrib(default=None)
+ sampleRate = attrib(default=None)
+ bitDepth = attrib(default=None)
+ audioTrackFormat = attrib(default=None, repr=False)
+ audioPackFormat = attrib(default=None, repr=False)
+
+ audioTrackFormatIDRef = attrib(default=None)
+ audioPackFormatIDRef = attrib(default=None)
+
+ def lazy_lookup_references(self):
+ if self.audioTrackFormatIDRef is not None:
+ self.audioTrackFormat = self._lookup_element(self.audioTrackFormatIDRef)
+ self.audioTrackFormatIDRef = None
+ if self.audioPackFormatIDRef is not None:
+ self.audioPackFormat = self._lookup_element(self.audioPackFormatIDRef)
+ self.audioPackFormatIDRef = None
diff --git a/ear/fileio/adm/exceptions.py b/ear/fileio/adm/exceptions.py
new file mode 100644
index 00000000..aefb4c57
--- /dev/null
+++ b/ear/fileio/adm/exceptions.py
@@ -0,0 +1,54 @@
+class AdmError(Exception):
+ """Base class for ADM parsing exceptions."""
+ pass
+
+
+class AdmMissingRequiredElement(AdmError):
+ """Exception raised for missing required elements.
+
+ Parameters:
+ message -- explanation of the error
+ """
+
+ def __init__(self, message):
+ self.message = message
+
+
+class AdmIDError(AdmError):
+ """Exception raised when errors relating to IDs are identified.
+
+ Parameters:
+ message (str): explanation of the error
+ """
+ def __init__(self, message):
+ self.message = message
+
+
+class AdmWarning(Warning):
+ """Base class for ADM parsing warnings."""
+ pass
+
+
+class AdmUnknownAttribute(AdmWarning):
+ """Warning raised for unknown attributes
+
+ Parameters:
+ element -- the element containing the attribute
+ attribute -- the unknown attribute
+ """
+
+ def __init__(self, message):
+ self.message = message
+
+ def __str__(self):
+ return self.message
+
+
+class AdmIDWarning(AdmWarning):
+ """Warning raised when issues relating to IDs are identified.
+
+ Parameters:
+ message (str): explanation of the error
+ """
+ def __init__(self, message):
+ self.message = message
diff --git a/ear/fileio/adm/generate_ids.py b/ear/fileio/adm/generate_ids.py
new file mode 100644
index 00000000..c45f21d0
--- /dev/null
+++ b/ear/fileio/adm/generate_ids.py
@@ -0,0 +1,42 @@
+def non_common(elements):
+ for element in elements:
+ if not element.is_common_definition:
+ yield element
+
+
+def generate_ids(adm):
+ """regenerate ids for all elements in adm"""
+ # clear track format ids so that we can check these have all been allocated
+ for element in non_common(adm.audioTrackFormats):
+ element.id = None
+
+ for id, element in enumerate(adm.audioProgrammes, 0x1001):
+ element.id = "APR_{id:04X}".format(id=id)
+
+ for id, element in enumerate(adm.audioContents, 0x1001):
+ element.id = "ACO_{id:04X}".format(id=id)
+
+ for id, element in enumerate(adm.audioObjects, 0x1001):
+ element.id = "AO_{id:04X}".format(id=id)
+
+ for id, element in enumerate(non_common(adm.audioPackFormats), 0x1001):
+ element.id = "AP_{type.value:04X}{id:04X}".format(id=id, type=element.type)
+
+ for id, element in enumerate(non_common(adm.audioChannelFormats), 0x1001):
+ element.id = "AC_{type.value:04X}{id:04X}".format(id=id, type=element.type)
+
+ for block_id, block in enumerate(element.audioBlockFormats, 0x1):
+ block.id = "AB_{type.value:04X}{id:04X}_{block_id:08X}".format(id=id, type=element.type, block_id=block_id)
+
+ for id, element in enumerate(non_common(adm.audioStreamFormats), 0x1001):
+ element.id = "AS_{format.value:04X}{id:04X}".format(id=id, format=element.format)
+
+ for track_id, element in enumerate(non_common(element.audioTrackFormats), 0x1):
+ element.id = "AT_{format.value:04X}{id:04X}_{track_id:02X}".format(id=id, format=element.format, track_id=track_id)
+
+ for id, element in enumerate(adm.audioTrackUIDs):
+ element.id = "ATU_{id:08X}".format(id=id)
+
+ # check for ant track uids that have not been allocated
+ for element in non_common(adm.audioTrackFormats):
+ assert element.id is not None, "cannot create id for audioTrackFormat not linked to any audioStreamFormat"
diff --git a/ear/fileio/adm/test/__init__.py b/ear/fileio/adm/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/fileio/adm/test/test_adm_files/ADM.xml b/ear/fileio/adm/test/test_adm_files/ADM.xml
new file mode 100644
index 00000000..bbc4772e
--- /dev/null
+++ b/ear/fileio/adm/test/test_adm_files/ADM.xml
@@ -0,0 +1,57 @@
+
+
+
+
+ audioProgrammeLanguage
+ start
+ end
+ maxDuckingDepth
+
+
+
+
+
+
+ audioTrackFormatID
+ audioTrackFormatName
+ formatLabel
+ formatDefinition
+
+
+ audioStreamFormatIDRef
+
+
+
+
+ audioStreamFormatID
+ audioStreamFormatName
+ formatLabel
+ formatDefinition
+
+
+ audioChannelFormatIDRef
+ audioPackFormatIDRef
+ audioTrackFormatIDRef
+
+
+
+
+ audioChannelFormatID
+ audioChannelFormatName
+ typeLabel
+ typeDefinition
+
+
+ audioBlockFormat
+ frequency
+
+
+
+
+ audioBlockFormatID
+ rtime
+ duration
+
+
+
+
diff --git a/ear/fileio/adm/test/test_adm_files/base.xml b/ear/fileio/adm/test/test_adm_files/base.xml
new file mode 100644
index 00000000..61b0329d
--- /dev/null
+++ b/ear/fileio/adm/test/test_adm_files/base.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ ACO_1001
+
+
+ AO_1001
+
+
+ AP_00031001
+ ATU_00000001
+
+
+ AC_00031001
+
+
+
+ 0.000000
+ 0.000000
+ 1.000000
+
+
+
+ AC_00031001
+ AT_00011001_01
+
+
+ AS_00011001
+
+
+ AT_00011001_01
+ AP_00031001
+
+
+
+
+
diff --git a/ear/fileio/adm/test/test_adm_files/example1.xml b/ear/fileio/adm/test/test_adm_files/example1.xml
new file mode 100644
index 00000000..44a6960c
--- /dev/null
+++ b/ear/fileio/adm/test/test_adm_files/example1.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+ AC_00011001
+ AC_00011002
+
+
+
+
+
+
+ M+30
+ 30.0
+ 0.0
+ 1.0
+
+
+
+
+ M-30
+ -30.0
+ 0.0
+ 1.0
+
+
+
+
+
+
+ AC_00011001
+ AT_00011001_01
+
+ AC_00011002
+ AT_00011002_01
+
+
+
+
+
+ AS_00011001
+
+
+ AS_00011002
+
+
+
+
+
+
+ ACO_1001
+ ACO_1002
+
+
+
+
+
+ AO_1001
+
+ -28.0
+
+
+
+ AO_1002
+
+ -23.0
+
+
+
+
+
+
+ AP_00011002
+ ATU_00000001
+ ATU_00000002
+
+
+ AP_00011002
+ ATU_00000003
+ ATU_00000004
+
+
+
+
+
+ AT_00011001_01
+ AP_00011002
+
+
+ AT_00011002_01
+ AP_00011002
+
+
+ AT_00011001_01
+ AP_00011002
+
+
+ AT_00011002_01
+ AP_00011002
+
+
+
+
diff --git a/ear/fileio/adm/test/test_adm_files/example1_bs2094.xml b/ear/fileio/adm/test/test_adm_files/example1_bs2094.xml
new file mode 100644
index 00000000..43f92db3
--- /dev/null
+++ b/ear/fileio/adm/test/test_adm_files/example1_bs2094.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+ ACO_1001
+ ACO_1002
+
+
+
+
+
+ AO_1001
+
+ -28.0
+
+
+
+ AO_1002
+
+ -23.0
+
+
+
+
+
+
+ AP_00010002
+ ATU_00000001
+ ATU_00000002
+
+
+ AP_00010002
+ ATU_00000003
+ ATU_00000004
+
+
+
+
+
+ AT_00010001_01
+ AP_00010002
+
+
+ AT_00010002_01
+ AP_00010002
+
+
+ AT_00010001_01
+ AP_00010002
+
+
+ AT_00010002_01
+ AP_00010002
+
+
+
+
diff --git a/ear/fileio/adm/test/test_adm_files/example5.xml b/ear/fileio/adm/test/test_adm_files/example5.xml
new file mode 100644
index 00000000..4d61ca5f
--- /dev/null
+++ b/ear/fileio/adm/test/test_adm_files/example5.xml
@@ -0,0 +1,704 @@
+
+
+
+
+
+
+
+
+ ACN_1001
+
+
+
+ ACN_1002
+
+
+
+
+
+
+
+
+
+ AO_1001
+
+ -24.0
+
+
+
+
+ AO_1002
+
+ -24.0
+
+
+
+
+
+
+
+
+ AP_00011009
+ ATU_00000001
+ ATU_00000002
+ ATU_00000003
+ ATU_00000004
+ ATU_00000005
+ ATU_00000006
+ ATU_00000007
+ ATU_00000008
+ ATU_00000009
+ ATU_0000000a
+ ATU_0000000b
+ ATU_0000000c
+ ATU_0000000d
+ ATU_0000000e
+ ATU_0000000f
+ ATU_00000010
+ ATU_00000011
+ ATU_00000012
+ ATU_00000013
+ ATU_00000014
+ ATU_00000015
+ ATU_00000016
+ ATU_00000017
+ ATU_00000018
+
+
+
+ AP_00011009
+ ATU_00000001
+ ATU_00000002
+ ATU_00000019
+ ATU_00000004
+ ATU_00000005
+ ATU_00000006
+ ATU_00000007
+ ATU_00000008
+ ATU_00000009
+ ATU_0000000a
+ ATU_0000000b
+ ATU_0000000c
+ ATU_0000000d
+ ATU_0000000e
+ ATU_0000000f
+ ATU_00000010
+ ATU_00000011
+ ATU_00000012
+ ATU_00000013
+ ATU_00000014
+ ATU_00000015
+ ATU_00000016
+ ATU_00000017
+ ATU_00000018
+
+
+
+
+
+
+
+ AC_00011001
+ AC_00011002
+ AC_00011003
+ AC_00011004
+ AC_00011007
+ AC_00011008
+ AC_00011009
+ AC_0001100a
+ AC_0001100b
+ AC_0001100c
+ AC_0001100d
+ AC_0001100e
+ AC_0001100f
+ AC_00011010
+ AC_00011011
+ AC_00011012
+ AC_00011013
+ AC_00011014
+ AC_00011015
+ AC_00011016
+ AC_00011017
+ AC_00011018
+ AC_00011019
+ AC_0001101a
+
+
+
+
+
+
+
+
+
+ M+060
+ 60.0
+ 0.0
+ 1.0
+
+
+
+
+
+ M-060
+ -60.0
+ 0.0
+ 1.0
+
+
+
+
+
+ M+000
+ 0.0
+ 0.0
+ 1.0
+
+
+
+
+ 200
+
+ LFE+045
+ 45.0
+ -30.0
+ 1.0
+
+
+
+
+
+ M+135
+ 135.0
+ 0.0
+ 1.0
+
+
+
+
+
+ M-135
+ -135.0
+ 0.0
+ 1.0
+
+
+
+
+
+ M+030
+ 30.0
+ 0.0
+ 1.0
+
+
+
+
+
+ M-030
+ -30.0
+ 0.0
+ 1.0
+
+
+
+
+
+ M+180
+ 180.0
+ 0.0
+ 1.0
+
+
+
+
+ 200
+
+ LFE-045
+ -45.0
+ -30.0
+ 1.0
+
+
+
+
+
+ M+090
+ 90.0
+ 0.0
+ 1.0
+
+
+
+
+
+ M-090
+ -90.0
+ 0.0
+ 1.0
+
+
+
+
+
+ U+045
+ 45.0
+ 45.0
+ 1.0
+
+
+
+
+
+ U-045
+ -45.0
+ 45.0
+ 1.0
+
+
+
+
+
+ U+000
+ 0.0
+ 45.0
+ 1.0
+
+
+
+
+
+ T+000
+ 0.0
+ 90.0
+ 1.0
+
+
+
+
+
+ U+135
+ 135.0
+ 45.0
+ 1.0
+
+
+
+
+
+ U-135
+ -135.0
+ 45.0
+ 1.0
+
+
+
+
+
+ U+090
+ 90.0
+ 45.0
+ 1.0
+
+
+
+
+
+ U-090
+ -90.0
+ 45.0
+ 1.0
+
+
+
+
+
+ U+180
+ 180.0
+ 45.0
+ 1.0
+
+
+
+
+
+ B+000
+ 0.0
+ -30.0
+ 1.0
+
+
+
+
+
+ B+045
+ 45.0
+ -30.0
+ 1.0
+
+
+
+
+
+ B-045
+ -45.0
+ -30.0
+ 1.0
+
+
+
+
+
+
+
+
+ AC_00011001
+ AT_00011001_01
+
+
+
+ AC_00011002
+ AT_00011002_01
+
+
+
+ AC_00011003
+ AT_00011003_01
+
+
+
+ AC_00011004
+ AT_00011004_01
+
+
+
+ AC_00011007
+ AT_00011007_01
+
+
+
+ AC_00011008
+ AT_00011008_01
+
+
+
+ AC_00011009
+ AT_00011009_01
+
+
+
+ AC_0001100a
+ AT_0001100a_01
+
+
+
+ AC_0001100b
+ AT_0001100b_01
+
+
+
+ AC_0001100c
+ AT_0001100c_01
+
+
+
+ AC_0001100d
+ AT_0001100d_01
+
+
+
+ AC_0001100e
+ AT_0001100e_01
+
+
+
+ AC_0001100f
+ AT_0001100f_01
+
+
+
+ AC_00011010
+ AT_00011010_01
+
+
+
+ AC_00011011
+ AT_00011011_01
+
+
+
+ AC_00011012
+ AT_00011012_01
+
+
+
+ AC_00011013
+ AT_00011013_01
+
+
+
+ AC_00011014
+ AT_00011014_01
+
+
+
+ AC_00011015
+ AT_00011015_01
+
+
+
+ AC_00011016
+ AT_00011016_01
+
+
+
+ AC_00011017
+ AT_00011017_01
+
+
+
+ AC_00011018
+ AT_00011018_01
+
+
+
+ AC_00011019
+ AT_00011019_01
+
+
+
+ AC_0001101a
+ AT_0001101a_01
+
+
+
+
+
+
+
+ AS_00011001
+
+
+
+ AS_00011002
+
+
+
+ AS_00011003
+
+
+
+ AS_00011004
+
+
+
+ AS_00011007
+
+
+
+ AS_00011008
+
+
+
+ AS_00011009
+
+
+
+ AS_0001100a
+
+
+
+ AS_0001100b
+
+
+
+ AS_0001100c
+
+
+
+ AS_0001100d
+
+
+
+ AS_0001100e
+
+
+
+ AS_0001100f
+
+
+
+ AS_00011010
+
+
+
+ AS_00011011
+
+
+
+ AS_00011012
+
+
+
+ AS_00011013
+
+
+
+ AS_00011014
+
+
+
+ AS_00011015
+
+
+
+ AS_00011016
+
+
+
+ AS_00011017
+
+
+
+ AS_00011018
+
+
+
+ AS_00011019
+
+
+
+ AS_0001101a
+
+
+
+
+
+
+
+
+ AT_00011001_01
+ AP_00011009
+
+
+
+ AT_00011002_01
+ AP_00011009
+
+
+
+ AT_00011003_01
+ AP_00011009
+
+
+
+ AT_00011004_01
+ AP_00011009
+
+
+
+ AT_00011007_01
+ AP_00011009
+
+
+
+ AT_00011008_01
+ AP_00011009
+
+
+
+ AT_00011009_01
+ AP_00011009
+
+
+
+ AT_0001100a_01
+ AP_00011009
+
+
+
+ AT_0001100b_01
+ AP_00011009
+
+
+
+ AT_0001100c_01
+ AP_00011009
+
+
+
+ AT_0001100d_01
+ AP_00011009
+
+
+
+ AT_0001100e_01
+ AP_00011009
+
+
+
+ AT_0001100f_01
+ AP_00011009
+
+
+
+ AT_00011010_01
+ AP_00011009
+
+
+
+ AT_00011011_01
+ AP_00011009
+
+
+
+ AT_00011012_01
+ AP_00011009
+
+
+
+ AT_00011013_01
+ AP_00011009
+
+
+
+ AT_00011014_01
+ AP_00011009
+
+
+
+ AT_00011015_01
+ AP_00011009
+
+
+
+ AT_00011016_01
+ AP_00011009
+
+
+
+ AT_00011017_01
+ AP_00011009
+
+
+
+ AT_00011018_01
+ AP_00011009
+
+
+
+ AT_00011019_01
+ AP_00011009
+
+
+
+ AT_0001101a_01
+ AP_00011009
+
+
+
+ AT_00011003_01
+ AP_00011009
+
+
+
diff --git a/ear/fileio/adm/test/test_bs2094.py b/ear/fileio/adm/test/test_bs2094.py
new file mode 100644
index 00000000..54464d4d
--- /dev/null
+++ b/ear/fileio/adm/test/test_bs2094.py
@@ -0,0 +1,13 @@
+from ..adm import ADM
+from ..common_definitions import load_common_definitions
+
+
+def test_load_common_definitions():
+ adm = ADM()
+ load_common_definitions(adm)
+
+ assert len(adm.audioChannelFormats) == 300
+ assert len(adm.audioPackFormats) == 43
+ assert len(adm.audioTrackFormats) == 300
+ assert len(adm.audioStreamFormats) == 300
+ assert len(list(adm.elements)) == (300 + 43 + 300 + 300)
diff --git a/ear/fileio/adm/test/test_chna.py b/ear/fileio/adm/test/test_chna.py
new file mode 100644
index 00000000..80c269af
--- /dev/null
+++ b/ear/fileio/adm/test/test_chna.py
@@ -0,0 +1,68 @@
+import pytest
+from ..chna import load_chna_chunk, populate_chna_chunk
+from ..adm import ADM
+from ..elements import AudioTrackUID
+from ..common_definitions import load_common_definitions
+from ...bw64.chunks import ChnaChunk, AudioID
+
+
+def test_load():
+ adm = ADM()
+ load_common_definitions(adm)
+ chna = ChnaChunk()
+
+ # normal use
+ chna.audioIDs = [AudioID(1, "ATU_00000001", "AT_00010001_01", "AP_00010002")]
+ load_chna_chunk(adm, chna)
+ assert adm.audioTrackUIDs[0].trackIndex == 1
+ assert adm.audioTrackUIDs[0].id == "ATU_00000001"
+ assert adm.audioTrackUIDs[0].audioTrackFormat is adm["AT_00010001_01"]
+ assert adm.audioTrackUIDs[0].audioPackFormat is adm["AP_00010002"]
+
+ # missing pack ref
+ chna.audioIDs = [AudioID(2, "ATU_00000002", "AT_00010002_01", None)]
+ load_chna_chunk(adm, chna)
+ assert adm.audioTrackUIDs[1].audioPackFormat is None
+
+ # inconsistent pack ref
+ chna.audioIDs = [AudioID(1, "ATU_00000001", "AT_00010002_01", "AP_00010002")]
+ with pytest.raises(Exception) as excinfo:
+ load_chna_chunk(adm, chna)
+ assert str(excinfo.value) == ("Error in track UID ATU_00000001: audioTrackFormatIDRef in CHNA, "
+ "'AT_00010002_01' does not match value in AXML, 'AT_00010001_01'.")
+
+ # inconsistent track ref
+ chna.audioIDs = [AudioID(1, "ATU_00000001", "AT_00010001_01", "AP_00010003")]
+ with pytest.raises(Exception) as excinfo:
+ load_chna_chunk(adm, chna)
+ assert str(excinfo.value) == ("Error in track UID ATU_00000001: audioPackFormatIDRef in CHNA, "
+ "'AP_00010003' does not match value in AXML, 'AP_00010002'.")
+
+
+def test_populate():
+ adm = ADM()
+ load_common_definitions(adm)
+ chna = ChnaChunk()
+
+ # normal use
+ adm.addAudioTrackUID(AudioTrackUID(
+ id="ATU_00000001",
+ trackIndex=1,
+ audioTrackFormat=adm["AT_00010001_01"],
+ audioPackFormat=adm["AP_00010002"]))
+
+ # missing pack format
+ adm.addAudioTrackUID(AudioTrackUID(
+ id="ATU_00000002",
+ trackIndex=2,
+ audioTrackFormat=adm["AT_00010002_01"]))
+
+ populate_chna_chunk(chna, adm)
+ assert chna.audioIDs == [AudioID(audioTrackUID="ATU_00000001",
+ trackIndex=1,
+ audioTrackFormatIDRef="AT_00010001_01",
+ audioPackFormatIDRef="AP_00010002"),
+ AudioID(audioTrackUID="ATU_00000002",
+ trackIndex=2,
+ audioTrackFormatIDRef="AT_00010002_01",
+ audioPackFormatIDRef=None)]
diff --git a/ear/fileio/adm/test/test_xml.py b/ear/fileio/adm/test/test_xml.py
new file mode 100644
index 00000000..d61fbbb3
--- /dev/null
+++ b/ear/fileio/adm/test/test_xml.py
@@ -0,0 +1,521 @@
+import lxml.etree
+from lxml.builder import ElementMaker
+from fractions import Fraction
+import pytest
+import re
+from copy import deepcopy
+from ..xml import parse_string, adm_to_xml, ParseError
+from ..elements import AudioBlockFormatBinaural, CartesianZone, PolarZone
+from ....common import CartesianPosition, PolarPosition, CartesianScreen, PolarScreen
+
+ns = "urn:ebu:metadata-schema:ebuCore_2015"
+nsmap = dict(adm=ns)
+E = ElementMaker(namespace=ns, nsmap=nsmap)
+
+
+# load base.xml as a starting point
+def load_base():
+ import pkg_resources
+ fname = "test_adm_files/base.xml"
+ with pkg_resources.resource_stream(__name__, fname) as xml_file:
+ return lxml.etree.parse(xml_file)
+
+
+base_xml = load_base()
+base_adm = parse_string(lxml.etree.tostring(base_xml))
+
+
+# xml modifications: these return a function that modifies an xml tree in some way
+
+def remove_children(xpath_to_children):
+ def f(xml):
+ for el in xml.xpath(xpath_to_children, namespaces=nsmap):
+ el.getparent().remove(el)
+ return f
+
+
+def add_children(xpath_to_parent, *new_children):
+ def f(xml):
+ parent = xml.xpath(xpath_to_parent, namespaces=nsmap)[0]
+ parent.extend(new_children)
+ return f
+
+
+def set_attrs(xpath_to_el, **attrs):
+ def f(xml):
+ for element in xml.xpath(xpath_to_el, namespaces=nsmap):
+ element.attrib.update(attrs)
+ return f
+
+
+def del_attrs(xpath_to_el, *attrs):
+ def f(xml):
+ for element in xml.xpath(xpath_to_el, namespaces=nsmap):
+ for attr in attrs:
+ del element.attrib[attr]
+ return f
+
+
+def base_with_mods(*mods):
+ """A copy of the base xml, with some modifications applied"""
+ xml = deepcopy(base_xml)
+
+ for mod in mods:
+ mod(xml)
+
+ return xml
+
+
+def get_acf(adm):
+ """Get the first non-common-definition channel format."""
+ for cf in adm.audioChannelFormats:
+ if not cf.is_common_definition:
+ return cf
+
+
+def parsed_adm_after_mods(*mods):
+ """Apply modifications to base, stringify it, and run it though the parser."""
+ if mods:
+ xml = base_with_mods(*mods)
+ xml_str = lxml.etree.tostring(xml)
+ adm = parse_string(xml_str)
+ check_round_trip(adm)
+ return adm
+ else:
+ return base_adm
+
+
+def parsed_bf_after_mods(*mods):
+ adm = parsed_adm_after_mods(*mods)
+ return get_acf(adm).audioBlockFormats[0]
+
+
+def parsed_prog_after_mods(*mods):
+ adm = parsed_adm_after_mods(*mods)
+ return adm.audioProgrammes[0]
+
+
+bf_path = "//adm:audioBlockFormat"
+
+
+def test_gain():
+ assert parsed_bf_after_mods(add_children(bf_path, E.gain("0"))).gain == 0.0
+ assert parsed_bf_after_mods(add_children(bf_path, E.gain("0.5"))).gain == 0.5
+ assert parsed_bf_after_mods().gain == 1.0
+
+
+def test_extent():
+ assert parsed_bf_after_mods().width == 0.0
+ assert parsed_bf_after_mods().height == 0.0
+ assert parsed_bf_after_mods().depth == 0.0
+
+ assert parsed_bf_after_mods(add_children(bf_path, E.width("0.5"))).width == 0.5
+ assert parsed_bf_after_mods(add_children(bf_path, E.height("0.5"))).height == 0.5
+ assert parsed_bf_after_mods(add_children(bf_path, E.depth("0.5"))).depth == 0.5
+
+
+def test_channel_lock():
+ assert parsed_bf_after_mods().channelLock is None
+
+ block_format = parsed_bf_after_mods(add_children(bf_path, E.channelLock("1")))
+ assert block_format.channelLock is not None
+ assert block_format.channelLock.maxDistance is None
+
+ block_format = parsed_bf_after_mods(add_children(bf_path, E.channelLock("1", maxDistance="0.5")))
+ assert block_format.channelLock is not None
+ assert block_format.channelLock.maxDistance == 0.5
+
+
+def test_jump_position():
+ assert parsed_bf_after_mods().jumpPosition.flag is False
+ assert parsed_bf_after_mods().jumpPosition.interpolationLength is None
+
+ block_format = parsed_bf_after_mods(add_children(bf_path, E.jumpPosition("1")))
+ assert block_format.jumpPosition.flag is True
+ assert block_format.jumpPosition.interpolationLength is None
+
+ block_format = parsed_bf_after_mods(add_children(bf_path, E.jumpPosition("1", interpolationLength="0.5")))
+ assert block_format.jumpPosition.flag is True
+ assert block_format.jumpPosition.interpolationLength == Fraction(1, 2)
+
+
+def test_divergence():
+ assert parsed_bf_after_mods().objectDivergence is None
+
+ block_format = parsed_bf_after_mods(add_children(bf_path, E.objectDivergence("0.5")))
+ assert block_format.objectDivergence.value == 0.5
+ assert block_format.objectDivergence.azimuthRange is None
+ assert block_format.objectDivergence.positionRange is None
+
+ block_format = parsed_bf_after_mods(add_children(bf_path, E.objectDivergence("0.5", azimuthRange="30")))
+ assert block_format.objectDivergence.value == 0.5
+ assert block_format.objectDivergence.azimuthRange == 30.0
+ assert block_format.objectDivergence.positionRange is None
+
+ block_format = parsed_bf_after_mods(add_children(bf_path, E.objectDivergence("0.5", positionRange="0.5")))
+ assert block_format.objectDivergence.value == 0.5
+ assert block_format.objectDivergence.positionRange == 0.5
+ assert block_format.objectDivergence.azimuthRange is None
+
+
+def test_polar_position():
+ block_format = parsed_bf_after_mods(remove_children("//adm:position"),
+ add_children(bf_path,
+ E.position("10", coordinate="azimuth", screenEdgeLock="right"),
+ E.position("20", coordinate="elevation", screenEdgeLock="top"),
+ E.position("0.5", coordinate="distance")))
+
+ assert block_format.position.azimuth == 10.0
+ assert block_format.position.elevation == 20.0
+ assert block_format.position.distance == 0.5
+ assert block_format.position.screenEdgeLock.horizontal == "right"
+ assert block_format.position.screenEdgeLock.vertical == "top"
+
+
+def test_cart_position():
+ block_format = parsed_bf_after_mods(remove_children("//adm:position"),
+ add_children(bf_path,
+ E.position("0.2", coordinate="X"),
+ E.position("0.3", coordinate="Y"),
+ E.position("0.5", coordinate="Z")))
+
+ assert block_format.position.X == 0.2
+ assert block_format.position.Y == 0.3
+ assert block_format.position.Z == 0.5
+
+
+def test_exceptions():
+ # error in element value converter
+ with pytest.raises(ParseError) as excinfo:
+ parsed_bf_after_mods(add_children(bf_path, E.gain("g")))
+ expected = "error while parsing element gain on line [0-9]+: ValueError: could not convert string to float: '?g'?$"
+ assert re.match(expected, str(excinfo.value)) is not None
+
+ # error in attribute converter
+ with pytest.raises(ParseError) as excinfo:
+ parsed_bf_after_mods(set_attrs(bf_path, rtime="t"))
+ expected = "error while parsing attr rtime of element audioBlockFormat on line [0-9]+: ValueError: Cannot parse time: 't'$"
+ assert re.match(expected, str(excinfo.value)) is not None
+
+ # missing items
+ with pytest.raises(ParseError) as excinfo:
+ parsed_bf_after_mods(del_attrs(bf_path, "audioBlockFormatID"))
+ expected = "error while parsing element audioBlockFormat on line [0-9]+: ValueError: missing items: audioBlockFormatID$"
+ assert re.match(expected, str(excinfo.value)) is not None
+
+ # multiple elements
+ with pytest.raises(ParseError) as excinfo:
+ parsed_bf_after_mods(add_children(bf_path, E.gain("1.0"), E.gain("1.0")))
+ expected = "error while parsing element gain on line [0-9]+: ValueError: multiple gain elements found$"
+ assert re.match(expected, str(excinfo.value)) is not None
+
+
+def test_cartesian():
+ assert parsed_bf_after_mods().cartesian is False
+ assert parsed_bf_after_mods(add_children(bf_path, E.cartesian("0"))).cartesian is False
+ assert parsed_bf_after_mods(add_children(bf_path, E.cartesian("1"))).cartesian is True
+
+
+def test_diffuse():
+ assert parsed_bf_after_mods().diffuse == 0.0
+ assert parsed_bf_after_mods(add_children(bf_path, E.diffuse("0.5"))).diffuse == 0.5
+
+
+def test_screenRef():
+ assert parsed_bf_after_mods().screenRef is False
+ assert parsed_bf_after_mods(add_children(bf_path, E.screenRef("0"))).screenRef is False
+ assert parsed_bf_after_mods(add_children(bf_path, E.screenRef("1"))).screenRef is True
+
+
+def test_importance():
+ assert parsed_bf_after_mods().importance is 10
+ assert parsed_bf_after_mods(add_children(bf_path, E.importance("5"))).importance == 5
+
+
+def test_zone():
+ assert parsed_bf_after_mods().zoneExclusion == []
+ assert parsed_bf_after_mods(add_children(bf_path, E.zoneExclusion())).zoneExclusion == []
+ assert (parsed_bf_after_mods(add_children(bf_path,
+ E.zoneExclusion(E.zone(minX="-1.0",
+ minY="-0.9",
+ minZ="-0.8",
+ maxX="0.8",
+ maxY="0.9",
+ maxZ="1.0"),
+ E.zone(minElevation="-20", maxElevation="20",
+ minAzimuth="-30", maxAzimuth="30")))
+ ).zoneExclusion == [CartesianZone(minX=-1.0,
+ minY=-0.9,
+ minZ=-0.8,
+ maxX=0.8,
+ maxY=0.9,
+ maxZ=1.0),
+ PolarZone(minElevation=-20.0, maxElevation=20.0,
+ minAzimuth=-30.0, maxAzimuth=30.0)])
+
+
+def test_directspeakers():
+ def with_children(*children):
+ return parsed_bf_after_mods(
+ set_attrs("//adm:audioChannelFormat", typeDefinition="DirectSpeakers", typeLabel="001"),
+ remove_children("//adm:position"),
+ add_children(bf_path,
+ *children))
+
+ # test values and screen edge attributes
+ block_format = with_children(E.position("-29", coordinate="azimuth", screenEdgeLock="right"),
+ E.position("15", coordinate="elevation", screenEdgeLock="top"),
+ E.position("0.9", coordinate="distance"))
+ assert block_format.position.azimuth == -29.0
+ assert block_format.position.screenEdgeLock.horizontal == "right"
+ assert block_format.position.elevation == 15.0
+ assert block_format.position.screenEdgeLock.vertical == "top"
+ assert block_format.position.distance == 0.9
+
+ # distance defaults to 1
+ block_format = with_children(E.position("-29", coordinate="azimuth"),
+ E.position("15", coordinate="elevation"))
+ assert block_format.position.distance == 1.0
+
+ # test min and max
+ block_format = with_children(E.position("-29", coordinate="azimuth"),
+ E.position("-28", coordinate="azimuth", bound="max"),
+ E.position("-30", coordinate="azimuth", bound="min"),
+ E.position("15", coordinate="elevation"),
+ E.position("16", coordinate="elevation", bound="max"),
+ E.position("14", coordinate="elevation", bound="min"),
+ E.position("0.9", coordinate="distance"),
+ E.position("1.1", coordinate="distance", bound="max"),
+ E.position("0.8", coordinate="distance", bound="min"))
+ assert block_format.position.azimuth == -29.0
+ assert block_format.position.bounded_azimuth.max == -28.0
+ assert block_format.position.bounded_azimuth.min == -30.0
+ assert block_format.position.elevation == 15.0
+ assert block_format.position.bounded_elevation.max == 16.0
+ assert block_format.position.bounded_elevation.min == 14.0
+ assert block_format.position.distance == 0.9
+ assert block_format.position.bounded_distance.max == 1.1
+ assert block_format.position.bounded_distance.min == 0.8
+
+ # test Cartesian
+ block_format = with_children(
+ E.position("0.1", coordinate="X"),
+ E.position("0.2", coordinate="X", bound="max"),
+ E.position("0.3", coordinate="X", bound="min"),
+ E.position("0.4", coordinate="Y"),
+ E.position("0.5", coordinate="Y", bound="max"),
+ E.position("0.6", coordinate="Y", bound="min"),
+ E.position("0.7", coordinate="Z"),
+ E.position("0.8", coordinate="Z", bound="max"),
+ E.position("0.9", coordinate="Z", bound="min"),
+ )
+ assert block_format.position.X == 0.1
+ assert block_format.position.bounded_X.max == 0.2
+ assert block_format.position.bounded_X.min == 0.3
+ assert block_format.position.Y == 0.4
+ assert block_format.position.bounded_Y.max == 0.5
+ assert block_format.position.bounded_Y.min == 0.6
+ assert block_format.position.Z == 0.7
+ assert block_format.position.bounded_Z.max == 0.8
+ assert block_format.position.bounded_Z.min == 0.9
+
+ # test speaker label
+ for labels in ([], ["U-030"], ["U-030", "U-SC"]):
+ block_format = with_children(E.position("-29", coordinate="azimuth"),
+ E.position("15", coordinate="elevation"),
+ *map(E.speakerLabel, labels))
+ assert block_format.speakerLabel == labels
+
+
+def test_frequency():
+ def cf_with_children(*children):
+ adm = parsed_adm_after_mods(
+ add_children("//adm:audioChannelFormat", *children))
+ return get_acf(adm)
+
+ # check defaults
+ cf = cf_with_children()
+ assert cf.frequency.lowPass is None and cf.frequency.highPass is None
+
+ # both defined
+ cf = cf_with_children(E.frequency("500", typeDefinition="lowPass"),
+ E.frequency("100", typeDefinition="highPass"))
+ assert cf.frequency.lowPass == 500.0 and cf.frequency.highPass == 100.0
+
+ # check type
+ with pytest.raises(ParseError) as excinfo:
+ cf_with_children(E.frequency("500", typeDefinition="fooPass"))
+ expected = "error while parsing element frequency on line [0-9]+: ValueError: frequency type must be lowPass or highPass, not fooPass"
+ assert re.match(expected, str(excinfo.value)) is not None
+
+ # check repeated
+ for type in "lowPass", "highPass":
+ with pytest.raises(ParseError) as excinfo:
+ cf_with_children(E.frequency("500", typeDefinition=type),
+ E.frequency("500", typeDefinition=type))
+ expected = "error while parsing element frequency on line [0-9]+: ValueError: More than one {} frequency element specified.".format(type)
+ assert re.match(expected, str(excinfo.value)) is not None
+
+
+def test_binaural():
+ block_format = parsed_bf_after_mods(
+ set_attrs("//adm:audioChannelFormat", typeDefinition="Binaural", typeLabel="005"),
+ remove_children("//adm:position"))
+
+ assert isinstance(block_format, AudioBlockFormatBinaural)
+
+
+def test_hoa():
+ def with_children(*children):
+ return parsed_bf_after_mods(
+ set_attrs("//adm:audioChannelFormat", typeDefinition="HOA", typeLabel="004"),
+ remove_children("//adm:position"),
+ add_children(bf_path, *children))
+
+ # normal usage
+ block_format = with_children(E.order("1"), E.degree("-1"))
+ assert block_format.equation is None
+ assert block_format.order == 1
+ assert block_format.degree == -1
+ assert block_format.normalization == "SN3D"
+ assert block_format.nfcRefDist is None
+ assert block_format.screenRef is False
+
+ # explicit defaults
+ block_format = with_children(E.normalization("SN3D"), E.nfcRefDist("0.0"), E.screenRef("0"))
+ assert block_format.normalization == "SN3D"
+ assert block_format.nfcRefDist is None # adm says that 0 is same as unspecified
+ assert block_format.screenRef is False
+
+ # specify everything
+ block_format = with_children(E.equation("eqn"), E.normalization("N3D"), E.nfcRefDist("0.5"), E.screenRef("1"))
+ assert block_format.equation == "eqn"
+ assert block_format.normalization == "N3D"
+ assert block_format.nfcRefDist == 0.5
+ assert block_format.screenRef is True
+
+
+def test_referenceScreen():
+ assert parsed_prog_after_mods().referenceScreen == PolarScreen(
+ aspectRatio=1.78,
+ centrePosition=PolarPosition(
+ azimuth=0.0,
+ elevation=0.0,
+ distance=1.0),
+ widthAzimuth=58.0,
+ )
+
+ # Cartesian representation
+ assert (parsed_prog_after_mods(add_children("//adm:audioProgramme",
+ E.audioProgrammeReferenceScreen(E.aspectRatio("2.5"),
+ E.screenWidth(X="1.5"),
+ E.screenCentrePosition(
+ X="0.0",
+ Y="1.0",
+ Z="0.0")))
+ ).referenceScreen ==
+ CartesianScreen(aspectRatio=2.5,
+ centrePosition=CartesianPosition(
+ X=0.0,
+ Y=1.0,
+ Z=0.0),
+ widthX=1.5))
+
+ # Polar representation
+ assert (parsed_prog_after_mods(add_children("//adm:audioProgramme",
+ E.audioProgrammeReferenceScreen(E.aspectRatio("2.5"),
+ E.screenWidth(azimuth="45.0"),
+ E.screenCentrePosition(
+ azimuth="0.0",
+ elevation="0.0",
+ distance="1.0")))
+ ).referenceScreen ==
+ PolarScreen(aspectRatio=2.5,
+ centrePosition=PolarPosition(
+ azimuth=0.0,
+ elevation=0.0,
+ distance=1.0),
+ widthAzimuth=45.0))
+
+ # mixed types
+ with pytest.raises(ParseError) as excinfo:
+ parsed_prog_after_mods(add_children("//adm:audioProgramme",
+ E.audioProgrammeReferenceScreen(E.aspectRatio("2.5"),
+ E.screenWidth(azimuth="45.0"),
+ E.screenCentrePosition(
+ X="0.0",
+ Y="1.0",
+ Z="0.0"))))
+ expected = ("error while parsing element screenCentrePosition on line [0-9]+: ValueError: "
+ "Expected polar screen data, got cartesian.$")
+ assert re.match(expected, str(excinfo.value)) is not None
+
+ # missing keys in position
+ with pytest.raises(ParseError) as excinfo:
+ parsed_prog_after_mods(add_children("//adm:audioProgramme",
+ E.audioProgrammeReferenceScreen(E.aspectRatio("2.5"),
+ E.screenWidth(azimuth="45.0"),
+ E.screenCentrePosition(
+ X="0.0",
+ Y="1.0",
+ Q="0.0"))))
+ expected = ("error while parsing element screenCentrePosition on line [0-9]+: "
+ "ValueError: Do not know how to parse a screenCentrePosition with keys Q, X, Y.$")
+ assert re.match(expected, str(excinfo.value)) is not None
+
+ # missing key in width
+ with pytest.raises(ParseError) as excinfo:
+ parsed_prog_after_mods(add_children("//adm:audioProgramme",
+ E.audioProgrammeReferenceScreen(E.aspectRatio("2.5"),
+ E.screenWidth(az="45.0"),
+ E.screenCentrePosition(
+ X="0.0",
+ Y="1.0",
+ Z="0.0"))))
+ expected = ("error while parsing element screenWidth on line [0-9]+: "
+ "ValueError: Do not know how to parse a screenWidth with keys az.$")
+ assert re.match(expected, str(excinfo.value)) is not None
+
+
+def as_dict(inst):
+ """Turn an adm element into a dict to be used for comparison.
+
+ Object references are turned into the IDs of the objects being referred
+ to, and ID references are ignored; parent reference is ignored.
+ """
+ d = {}
+ from attr import fields
+ for field in fields(type(inst)):
+ if field.name.endswith("Ref"): continue
+ if field.name == "adm_parent": continue
+
+ value = getattr(inst, field.name)
+
+ if field.name == "audioBlockFormats":
+ value = list(map(as_dict, value))
+ if hasattr(value, "id"):
+ value = value.id
+ elif isinstance(value, list) and len(value) and hasattr(value[0], "id"):
+ value = [item.id for item in value]
+
+ d[field.name] = value
+
+ return d
+
+
+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)
+
+ assert len(list(parsed_adm.elements)) == len(list(adm.elements))
+
+ for element_orig, element_parsed in zip(
+ sorted(adm.elements, key=lambda el: el.id),
+ sorted(parsed_adm.elements, key=lambda el: el.id)):
+
+ assert as_dict(element_orig) == as_dict(element_parsed)
+
+
+def test_round_trip_base():
+ check_round_trip(base_adm)
diff --git a/ear/fileio/adm/time_format.py b/ear/fileio/adm/time_format.py
new file mode 100644
index 00000000..6ab2ec2d
--- /dev/null
+++ b/ear/fileio/adm/time_format.py
@@ -0,0 +1,41 @@
+from fractions import Fraction
+import re
+
+
+_TIME_RE = re.compile(r"""
+ (?P\d{1,2}) # one or two hour digits
+ : # :
+ (?P\d{1,2}) # one or two minute digits
+ : # :
+ (?P # second decimal consisting of:
+ \d{1,2} # two digits
+ (?: # then optionally
+ \. # a dot
+ \d* # and any number of digits
+ )
+ )
+ \Z # end
+""", re.VERBOSE)
+
+
+def parse_time(time_string):
+ match = _TIME_RE.match(time_string)
+ if match is None:
+ raise ValueError("Cannot parse time: {!r}".format(time_string))
+
+ hour = int(match.group("hour"))
+ minute = int(match.group("minute"))
+ second = Fraction(match.group("second"))
+
+ return ((hour * 60) + minute) * 60 + second
+
+
+def unparse_time(time):
+ minutes, seconds = divmod(time, 60)
+ hours, minutes = divmod(minutes, 60)
+
+ # XXX: conversion using float here is less than ideal, but there doesn't
+ # seem to be a better (easy) way; this should be accurate enough to
+ # maintain ns resolution, but should be revised
+ return "{hours:02d}:{minutes:02d}:{seconds:08.5f}".format(
+ hours=hours, minutes=minutes, seconds=float(seconds))
diff --git a/ear/fileio/adm/xml.py b/ear/fileio/adm/xml.py
new file mode 100644
index 00000000..2b234fb7
--- /dev/null
+++ b/ear/fileio/adm/xml.py
@@ -0,0 +1,1108 @@
+import sys
+import warnings
+from fractions import Fraction
+
+from attr import attrs, attrib, Factory, evolve
+import lxml.etree
+from lxml.etree import QName
+from lxml.builder import ElementMaker
+from six import viewkeys, iteritems, reraise
+
+from .adm import ADM
+from .elements import (
+ AudioBlockFormatObjects, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural, AudioBlockFormatHoa,
+ ChannelLock, BoundCoordinate, JumpPosition, ObjectDivergence, CartesianZone, PolarZone, ScreenEdgeLock)
+from .elements import (
+ AudioProgramme, AudioContent, AudioObject, AudioChannelFormat, AudioPackFormat, AudioStreamFormat, AudioTrackFormat, AudioTrackUID,
+ FormatDefinition, 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",
+ "urn:ebu:metadata-schema:ebuCore_2017",
+ "urn:ebu:metadata-schema:ebuCore",
+ "urn:metadata-schema:adm",
+ ]
+
+default_ns = "urn:ebu:metadata-schema:ebuCore_2017"
+default_nsmap = {None: default_ns}
+
+
+def qnames(localname):
+ """Get all qualified names with a given local name."""
+ return [localname if ns is None else lxml.etree.QName(ns, localname).text for ns in namespaces]
+
+
+def xpath(element, xpath):
+ """Select sub-elements from element with a given xpath, replacing '{ns}'
+ with each of the namespaces.
+ """
+ for ns in namespaces:
+ xpath_fmt = xpath.format(ns="" if ns is None else "adm:")
+ for found_element in element.xpath(xpath_fmt,
+ namespaces=dict() if ns is None else dict(adm=ns)):
+ yield found_element
+
+
+def text(element):
+ """Get the text of an element as a string."""
+ text = element.text
+ return text if text is not None else ""
+
+
+@attrs(cmp=False)
+class ParseError(Exception):
+ """Represents an error that occured while processing a piece of xml."""
+ exception = attrib()
+ element = attrib()
+ attr_name = attrib(default=None)
+
+ def __str__(self):
+ if self.attr_name is not None:
+ str_format = "error while parsing attr {attr_name} of element {tag} on line {line}: {exc_type}: {exc}"
+ else:
+ str_format = "error while parsing element {tag} on line {line}: {exc_type}: {exc}"
+
+ return str_format.format(
+ attr_name=self.attr_name,
+ tag=QName(self.element.tag).localname,
+ line=self.element.sourceline,
+ exc_type=type(self.exception).__name__,
+ exc=str(self.exception))
+
+
+@attrs
+class TypeConvert(object):
+ """Defines a conversion between some python type and strings."""
+ # function from string to type; None implies no conversion
+ loads = attrib()
+ # function from type to string; None implies no conversion
+ dumps = attrib()
+
+ # as above, but convert none to the identity function
+
+ @property
+ def loads_func(self):
+ if self.loads is None:
+ return lambda data: data
+ else:
+ return self.loads
+
+ @property
+ def dumps_func(self):
+ if self.dumps is None:
+ return lambda data: data
+ else:
+ return self.dumps
+
+
+def load_bool(data):
+ if data == "0":
+ return False
+ elif data == "1":
+ return True
+ else:
+ raise ValueError("could not parse bool: {}".format(data))
+
+
+StringType = TypeConvert(None, None)
+IntType = TypeConvert(int, str)
+FloatType = TypeConvert(float, "{:.5f}".format)
+SecondsType = TypeConvert(Fraction, lambda t: "{:07.5f}".format(float(t)))
+BoolType = TypeConvert(load_bool, "{:d}".format)
+TimeType = TypeConvert(parse_time, unparse_time)
+RefType = TypeConvert(loads=None,
+ dumps=lambda data: data.id)
+
+
+@attrs
+class Attribute(object):
+ """An xml attribute that is turned into a constructor kwarg, either directly
+ or via some conversion function."""
+ adm_name = attrib()
+ arg_name = attrib()
+ type = attrib(default=StringType)
+ required = attrib(default=False)
+ default = attrib(default=None)
+
+ def get_handlers(self):
+ arg_name = self.arg_name
+ convert = self.type.loads
+
+ if convert is None:
+ def f(kwargs, value):
+ kwargs[arg_name] = value
+ else:
+ def f(kwargs, value):
+ kwargs[arg_name] = convert(value)
+
+ return [("attr", self.adm_name, f)]
+
+ def to_xml(self, element, obj):
+ attr = getattr(obj, self.arg_name)
+ if attr != self.default:
+ element.attrib[self.adm_name] = self.type.dumps_func(attr)
+
+
+@attrs
+class Element(object):
+ """An xml sub-element; see sub-classes."""
+ adm_name = attrib()
+
+
+@attrs
+class ListElement(Element):
+ """An xml sub-element whose text contents is added to a list in the
+ constructor kwargs, either directly or via some conversion."""
+ arg_name = attrib()
+ attr_name = attrib(default=Factory(lambda self: self.arg_name, takes_self=True))
+ type = attrib(default=StringType)
+ required = attrib(default=False)
+
+ def get_handlers(self):
+ arg_name = self.arg_name
+ convert = self.type.loads
+
+ if convert is None:
+ def f(kwargs, element):
+ kwargs.setdefault(arg_name, []).append(text(element))
+ else:
+ def f(kwargs, element):
+ kwargs.setdefault(arg_name, []).append(convert(text(element)))
+
+ return [("element", qname, f) for qname in qnames(self.adm_name)]
+
+ def to_xml(self, element, obj):
+ for data in getattr(obj, self.attr_name):
+ new_el = element.makeelement(QName(default_ns, self.adm_name))
+ new_el.text = self.type.dumps_func(data)
+ element.append(new_el)
+
+
+@attrs
+class AttrElement(Element):
+ """An xml sub-element whose text contents is converted to a constructor
+ kwarg, either directly or via some conversion."""
+ arg_name = attrib()
+ attr_name = attrib(default=Factory(lambda self: self.arg_name, takes_self=True))
+ type = attrib(default=StringType)
+ required = attrib(default=False)
+ default = attrib(default=None)
+ parse_only = attrib(default=False)
+
+ def get_handlers(self):
+ arg_name = self.arg_name
+ convert = self.type.loads
+
+ if convert is None:
+ def f(kwargs, element):
+ if arg_name in kwargs:
+ raise ValueError("multiple {attr_name} elements found".format(attr_name=self.attr_name))
+ kwargs[arg_name] = text(element)
+ else:
+ def f(kwargs, element):
+ if arg_name in kwargs:
+ raise ValueError("multiple {attr_name} elements found".format(attr_name=self.attr_name))
+ kwargs[arg_name] = convert(text(element))
+
+ return [("element", qname, f) for qname in qnames(self.adm_name)]
+
+ def to_xml(self, element, obj):
+ if self.parse_only: return
+
+ attr = getattr(obj, self.attr_name)
+ if attr != self.default:
+ new_el = element.makeelement(QName(default_ns, self.adm_name))
+ new_el.text = self.type.dumps_func(attr)
+ element.append(new_el)
+
+
+@attrs
+class CustomElement(Element):
+ """An xml sub-element that is handled by some handler function."""
+ handler = attrib()
+ to_xml = attrib(default=None)
+ arg_name = attrib(default=None)
+ required = attrib(default=False)
+
+ def get_handlers(self):
+ return [("element", qname, self.handler) for qname in qnames(self.adm_name)]
+
+
+class ElementParser(object):
+ """Parser for an xml element type, that defers to the given properties to
+ handle the attributes and sub-elements."""
+
+ def __init__(self, cls, adm_name, properties):
+ self.cls = cls
+ self.adm_name = adm_name
+ self.properties = properties
+ self.attr_handlers = {}
+ self.element_handlers = {}
+ self.required_args = set()
+ self.arg_to_name = {}
+
+ for prop in properties:
+ for handler_type, adm_name, handler in prop.get_handlers():
+ if handler_type == "attr":
+ self.attr_handlers[adm_name] = handler
+ elif handler_type == "element":
+ self.element_handlers[adm_name] = handler
+ else:
+ assert False # pragma: no cover
+
+ if prop.required:
+ self.required_args.add(prop.arg_name)
+ self.arg_to_name[prop.arg_name] = QName(adm_name).localname
+
+ def parse(self, element):
+ kwargs = {}
+
+ def null_handler(kwargs, x):
+ pass
+
+ for key, value in iteritems(element.attrib):
+ try:
+ self.attr_handlers.get(key, null_handler)(kwargs, value)
+ except ParseError: raise
+ except Exception as e: reraise(ParseError, ParseError(e, element, key), sys.exc_info()[2])
+
+ for child in element.getchildren():
+ try:
+ self.element_handlers.get(child.tag, null_handler)(kwargs, child)
+ except ParseError: raise
+ except Exception as e: reraise(ParseError, ParseError(e, child), sys.exc_info()[2])
+
+ if not (viewkeys(kwargs) >= self.required_args):
+ missing_args = self.required_args - viewkeys(kwargs)
+ missing_items = (self.arg_to_name[arg_name] for arg_name in missing_args)
+ err = ValueError("missing items: {missing}".format(missing=', '.join(missing_items)))
+ raise ParseError(err, element)
+
+ try:
+ return self.cls(**kwargs)
+ except ParseError: raise
+ except Exception as e: reraise(ParseError, ParseError(e, element), sys.exc_info()[2])
+
+ def to_xml(self, parent, obj):
+ element = parent.makeelement(QName(default_ns, self.adm_name))
+ parent.append(element)
+
+ for prop in self.properties:
+ if prop.to_xml is not None:
+ prop.to_xml(element, obj)
+
+ return element
+
+ def as_handler(self, arg_name, default=None, required=False):
+ def handle(kwargs, el):
+ kwargs[arg_name] = self.parse(el)
+
+ def to_xml(parent, obj):
+ attr = getattr(obj, arg_name)
+ if attr is not None and (default is None or attr != default):
+ self.to_xml(parent, attr)
+
+ return CustomElement(
+ adm_name=self.adm_name,
+ handler=handle,
+ to_xml=to_xml,
+ arg_name=arg_name,
+ required=False,
+ )
+
+
+# helpers for common element types
+
+
+def RefList(name, **kwargs):
+ return ListElement(
+ adm_name=name + "IDRef",
+ arg_name=name + "IDRef",
+ attr_name=name + "s",
+ type=RefType,
+ **kwargs)
+
+
+def RefElement(name, **kwargs):
+ return AttrElement(
+ adm_name=name + "IDRef",
+ arg_name=name + "IDRef",
+ attr_name=name,
+ type=RefType,
+ **kwargs)
+
+
+@attrs
+class TypeAttribute(object):
+ """Attribute type used to convert *Label and *Definition attributes to and
+ from single enum attributes."""
+ enum = attrib()
+ definition_name = attrib()
+ label_name = attrib()
+ arg_name = attrib()
+ required = attrib(default=True)
+
+ def get_handlers(self):
+ def definition_handler(kwargs, value):
+ try:
+ found = self.enum[value]
+ except KeyError:
+ raise ValueError("Unknown {name}: {value}".format(name=self.definition_name, value=value))
+
+ if self.arg_name in kwargs and kwargs[self.arg_name] != found:
+ raise ValueError("Unexpected {name}: found {found.name}, expected {expected.name}".format(
+ name=self.definition_name,
+ found=found, expected=kwargs[self.arg_name]))
+
+ kwargs[self.arg_name] = found
+
+ def label_handler(kwargs, value):
+ try:
+ found = self.enum(int(value, 16))
+ except ValueError:
+ raise ValueError("Unknown {name}: {value}".format(name=self.label_name, value=value))
+
+ if self.arg_name in kwargs and kwargs[self.arg_name] != found:
+ raise ValueError("Unexpected {name}: found {found.value}, expected {expected.value}".format(
+ name=self.label_name,
+ found=found, expected=kwargs[self.arg_name]))
+
+ kwargs[self.arg_name] = found
+
+ return [("attr", self.definition_name, definition_handler),
+ ("attr", self.label_name, label_handler)]
+
+ def to_xml(self, element, obj):
+ attr = getattr(obj, self.arg_name)
+ element.attrib[self.label_name] = "{:04X}".format(attr.value)
+ element.attrib[self.definition_name] = attr.name
+
+
+type_handler = TypeAttribute(TypeDefinition, "typeDefinition", "typeLabel", "type")
+format_handler = TypeAttribute(FormatDefinition, "formatDefinition", "formatLabel", "format")
+
+
+# properties common to all block formats
+block_format_props = [
+ Attribute(adm_name="audioBlockFormatID", arg_name="id", required=True),
+ Attribute(adm_name="rtime", arg_name="rtime", type=TimeType),
+ Attribute(adm_name="duration", arg_name="duration", type=TimeType),
+]
+
+
+# typeDefinition == "Objects"
+
+def parse_block_format_objects(element):
+ props = block_format_objects_handler.parse(element)
+ props["position"] = parse_objects_position(element)
+ return AudioBlockFormatObjects(**props)
+
+
+def parse_objects_position(element):
+ position = {}
+
+ screen_edge_lock = ScreenEdgeLock()
+ for element in xpath(element, "{ns}position"):
+ try:
+ coordinate = element.attrib["coordinate"]
+ except KeyError:
+ raise ValueError("missing coordinate attr")
+ if coordinate in position:
+ raise ValueError("duplicate {coord} coordinates specified".format(coord=coordinate))
+
+ position[coordinate] = float(text(element))
+
+ if "screenEdgeLock" in element.attrib:
+ if element.attrib["coordinate"] in ["azimuth", "X"] and element.attrib["screenEdgeLock"] in ["left", "right"]:
+ screen_edge_lock.horizontal = element.attrib["screenEdgeLock"]
+ if element.attrib["coordinate"] in ["elevation", "Z"] and element.attrib["screenEdgeLock"] in ["top", "bottom"]:
+ screen_edge_lock.vertical = element.attrib["screenEdgeLock"]
+
+ coordinates = set(position.keys())
+
+ if coordinates in ({"azimuth", "elevation"}, {"azimuth", "elevation", "distance"}):
+ return ObjectPolarPosition(
+ azimuth=position['azimuth'],
+ elevation=position['elevation'],
+ distance=position.get('distance', 1.0),
+ screenEdgeLock=screen_edge_lock
+ )
+ elif coordinates == {"X", "Y", "Z"}:
+ return ObjectCartesianPosition(
+ X=position["X"],
+ Y=position["Y"],
+ Z=position["Z"],
+ screenEdgeLock=screen_edge_lock
+ )
+ else:
+ raise ValueError("Found coordinates {{{found}}}, but expected either "
+ "{{azimuth,elevation,distance}}, {{azimuth,elevation}} or {{X,Y,Z}}."
+ .format(found=','.join(coordinates)))
+
+
+def block_format_objects_to_xml(parent, obj):
+ element = block_format_objects_handler.to_xml(parent, obj)
+ object_position_to_xml(element, obj)
+
+
+def object_position_to_xml(parent, obj):
+ pos = obj.position
+
+ def dump_coordinate(coordinate, value, screenEdgeLock=None):
+ element = parent.makeelement(QName(default_ns, "position"), coordinate=coordinate)
+ element.text = FloatType.dumps(value)
+
+ if screenEdgeLock is not None:
+ element.attrib["screenEdgeLock"] = screenEdgeLock
+
+ parent.append(element)
+
+ if isinstance(pos, ObjectPolarPosition):
+ dump_coordinate("azimuth", pos.azimuth, pos.screenEdgeLock.horizontal)
+ dump_coordinate("elevation", pos.elevation, pos.screenEdgeLock.vertical)
+ if pos.distance != 1.0:
+ dump_coordinate("distance", pos.distance)
+ elif isinstance(pos, ObjectCartesianPosition):
+ dump_coordinate("X", pos.X, pos.screenEdgeLock.horizontal)
+ dump_coordinate("Y", pos.Y)
+ dump_coordinate("Z", pos.Z, pos.screenEdgeLock.vertical)
+ else:
+ assert False, "unexpected type" # pragma: no cover
+
+
+def handle_channel_lock(kwargs, el):
+ if text(el) == "0":
+ pass
+ elif text(el) == "1":
+ kwargs["channelLock"] = ChannelLock(
+ maxDistance=float(el.attrib["maxDistance"]) if "maxDistance" in el.attrib else None,
+ )
+ else:
+ raise ValueError("channelLock value must be 0 or 1, not {value!r}".format(value=text(el)))
+
+
+def channel_lock_to_xml(parent, obj):
+ channelLock = obj.channelLock
+
+ if channelLock is not None:
+ element = parent.makeelement(QName(default_ns, "channelLock"))
+ element.text = "1"
+
+ if channelLock.maxDistance is not None:
+ element.attrib["maxDistance"] = FloatType.dumps(channelLock.maxDistance)
+
+ parent.append(element)
+
+
+def handle_jump_position(kwargs, el):
+ kwargs["jumpPosition"] = JumpPosition(
+ flag=BoolType.loads(text(el)),
+ interpolationLength=(SecondsType.loads(el.attrib["interpolationLength"])
+ if "interpolationLength" in el.attrib else None))
+
+
+def jump_position_to_xml(parent, obj):
+ jumpPosition = obj.jumpPosition
+ if jumpPosition.flag:
+ element = parent.makeelement(QName(default_ns, "jumpPosition"))
+
+ element.text = BoolType.dumps(jumpPosition.flag)
+ if jumpPosition.interpolationLength is not None:
+ element.attrib["interpolationLength"] = SecondsType.dumps(jumpPosition.interpolationLength)
+
+ parent.append(element)
+
+
+def handle_divergence(kwargs, el):
+ kwargs["objectDivergence"] = ObjectDivergence(
+ value=float(text(el)),
+ azimuthRange=(float(el.attrib['azimuthRange'])
+ if 'azimuthRange' in el.attrib
+ else None),
+ positionRange=(float(el.attrib['positionRange'])
+ if 'positionRange' in el.attrib
+ else None))
+
+
+def divergence_to_xml(parent, obj):
+ divergence = obj.objectDivergence
+
+ if divergence is not None:
+ element = parent.makeelement(QName(default_ns, "objectDivergence"))
+ element.text = FloatType.dumps(divergence.value)
+ if divergence.azimuthRange is not None:
+ element.attrib["azimuthRange"] = FloatType.dumps(divergence.azimuthRange)
+ if divergence.positionRange is not None:
+ element.attrib["positionRange"] = FloatType.dumps(divergence.positionRange)
+ parent.append(element)
+
+
+def parse_zone(el):
+ keys = set(el.attrib.keys())
+ cart_keys = set(["minX", "minY", "minZ", "maxX", "maxY", "maxZ"])
+ polar_keys = set(["minAzimuth", "maxAzimuth", "minElevation", "maxElevation"])
+
+ if keys >= cart_keys and not keys & polar_keys:
+ return CartesianZone(**{key: float(value) for key, value in el.attrib.items()
+ if key in cart_keys})
+ elif keys >= polar_keys and not keys & cart_keys:
+ return PolarZone(**{key: float(value) for key, value in el.attrib.items()
+ if key in polar_keys})
+ else:
+ raise ValueError("Do not know how to parse a zone with keys {}.".format(", ".join(sorted(el.attrib.keys()))))
+
+
+def handle_zone(kwargs, el):
+ kwargs.setdefault("zones", []).append(parse_zone(el))
+
+
+def zone_to_xml(parent, obj):
+ if isinstance(obj, CartesianZone):
+ parent.append(parent.makeelement(QName(default_ns, "zone"),
+ minX=FloatType.dumps(obj.minX),
+ minY=FloatType.dumps(obj.minY),
+ minZ=FloatType.dumps(obj.minZ),
+ maxX=FloatType.dumps(obj.maxX),
+ maxY=FloatType.dumps(obj.maxY),
+ maxZ=FloatType.dumps(obj.maxZ)))
+ elif isinstance(obj, PolarZone):
+ parent.append(parent.makeelement(QName(default_ns, "zone"),
+ minAzimuth=FloatType.dumps(obj.minAzimuth),
+ maxAzimuth=FloatType.dumps(obj.maxAzimuth),
+ minElevation=FloatType.dumps(obj.minElevation),
+ maxElevation=FloatType.dumps(obj.maxElevation)))
+ else:
+ assert False, "unexpected type"
+
+
+def zones_to_xml(parent, obj):
+ for zone in obj:
+ zone_to_xml(parent, zone)
+
+
+zone_exclusion_handler = ElementParser((lambda zones=[]: zones), "zoneExclusion", [
+ CustomElement("zone", handle_zone, to_xml=zones_to_xml),
+])
+
+
+block_format_objects_handler = ElementParser(dict, "audioBlockFormat", block_format_props + [
+ CustomElement("channelLock", handle_channel_lock, to_xml=channel_lock_to_xml),
+ CustomElement("jumpPosition", handle_jump_position, to_xml=jump_position_to_xml),
+ CustomElement("objectDivergence", handle_divergence, to_xml=divergence_to_xml),
+ AttrElement(adm_name="width", arg_name="width", type=FloatType, default=0.0),
+ AttrElement(adm_name="height", arg_name="height", type=FloatType, default=0.0),
+ AttrElement(adm_name="depth", arg_name="depth", type=FloatType, default=0.0),
+ AttrElement(adm_name="gain", arg_name="gain", type=FloatType, default=1.0),
+ AttrElement(adm_name="diffuse", arg_name="diffuse", type=FloatType, default=0.0),
+ AttrElement(adm_name="cartesian", arg_name="cartesian", type=BoolType, default=False),
+ AttrElement(adm_name="screenRef", arg_name="screenRef", type=BoolType, default=False),
+ AttrElement(adm_name="importance", arg_name="importance", type=IntType, default=10),
+ zone_exclusion_handler.as_handler("zoneExclusion", default=[]),
+])
+
+
+# typeDefinition == "DirectSpeakers"
+
+
+def parse_speaker_position(element):
+ position = {}
+
+ screen_edge_lock = ScreenEdgeLock()
+ for element in xpath(element, "{ns}position"):
+ coordinate = element.attrib["coordinate"]
+
+ bound = element.attrib.get('bound', 'value')
+
+ position.setdefault(coordinate, {})[bound] = float(text(element))
+
+ if "screenEdgeLock" in element.attrib:
+ screenEdgeLock = element.attrib["screenEdgeLock"]
+ if bound != "value":
+ raise ValueError("screenEdgeLock must be specified on the position, not the bound")
+
+ if coordinate in ["azimuth", "X"] and screenEdgeLock in ["left", "right"]:
+ screen_edge_lock.horizontal = screenEdgeLock
+ elif coordinate in ["elevation", "Z"] and screenEdgeLock in ["top", "bottom"]:
+ screen_edge_lock.vertical = screenEdgeLock
+ else:
+ raise ValueError("invalid screenEdgeLock value {screenEdgeLock} for coordinate {coordinate}".format(
+ screenEdgeLock=screenEdgeLock,
+ coordinate=coordinate,
+ ))
+
+ coordinates = set(position.keys())
+
+ if coordinates in ({"azimuth", "elevation"}, {"azimuth", "elevation", "distance"}):
+ return DirectSpeakerPolarPosition(
+ bounded_azimuth=BoundCoordinate(**position['azimuth']),
+ bounded_elevation=BoundCoordinate(**position['elevation']),
+ bounded_distance=BoundCoordinate(**position.get('distance', dict(value=1.0))),
+ screenEdgeLock=screen_edge_lock
+ )
+ elif coordinates == {"X", "Y", "Z"}:
+ return DirectSpeakerCartesianPosition(
+ bounded_X=BoundCoordinate(**position["X"]),
+ bounded_Y=BoundCoordinate(**position["Y"]),
+ bounded_Z=BoundCoordinate(**position["Z"]),
+ screenEdgeLock=screen_edge_lock
+ )
+ else:
+ raise ValueError("Found coordinates {{{found}}}, but expected either "
+ "{{azimuth,elevation,distance}}, {{azimuth,elevation}} or {{X,Y,Z}}."
+ .format(found=','.join(coordinates)))
+
+
+def speaker_position_to_xml(parent, obj):
+ pos = obj.position
+
+ def add_pos_el(coordinate, value, **kwargs):
+ element = parent.makeelement(QName(default_ns, "position"), coordinate=coordinate, **kwargs)
+ element.text = FloatType.dumps(value)
+ parent.append(element)
+
+ def dump_bound(coordinate, bound, screen_edge_lock=None):
+ if screen_edge_lock is not None:
+ add_pos_el(coordinate, bound.value, screenEdgeLock=screen_edge_lock)
+ else:
+ add_pos_el(coordinate, bound.value)
+ if bound.max is not None:
+ add_pos_el(coordinate, bound.max, bound="max")
+ if bound.min is not None:
+ add_pos_el(coordinate, bound.min, bound="min")
+
+ if isinstance(pos, DirectSpeakerPolarPosition):
+ dump_bound("azimuth", pos.bounded_azimuth, pos.screenEdgeLock.horizontal)
+ dump_bound("elevation", pos.bounded_elevation, pos.screenEdgeLock.vertical)
+ if pos.bounded_distance != BoundCoordinate(1.0):
+ dump_bound("distance", pos.bounded_distance)
+ elif isinstance(pos, DirectSpeakerCartesianPosition):
+ dump_bound("X", pos.bounded_X, pos.screenEdgeLock.horizontal)
+ dump_bound("Y", pos.bounded_Y)
+ dump_bound("Z", pos.bounded_Z, pos.screenEdgeLock.vertical)
+ else:
+ assert False, "unexpected type" # pragma: no cover
+
+
+block_format_direct_speakers_handler = ElementParser(dict, "audioBlockFormat", block_format_props + [
+ ListElement(adm_name="speakerLabel", arg_name="speakerLabel"),
+])
+
+
+block_format_binaural_handler = ElementParser(AudioBlockFormatBinaural, "audioBlockFormat", block_format_props)
+
+
+def parse_block_format_direct_speakers(element):
+ props = block_format_direct_speakers_handler.parse(element)
+ props["position"] = parse_speaker_position(element)
+ return AudioBlockFormatDirectSpeakers(**props)
+
+
+def block_format_direct_speakers_to_xml(parent, obj):
+ element = block_format_direct_speakers_handler.to_xml(parent, obj)
+ speaker_position_to_xml(element, obj)
+
+
+def ref_dist_loads(data):
+ """load a float, but convert zero to None"""
+ value = FloatType.loads(data)
+ return value if value != 0.0 else None
+
+
+block_format_HOA_handler = ElementParser(AudioBlockFormatHoa, "audioBlockFormat", block_format_props + [
+ AttrElement(adm_name="equation", arg_name="equation", type=StringType),
+ AttrElement(adm_name="order", arg_name="order", type=IntType),
+ AttrElement(adm_name="degree", arg_name="degree", type=IntType),
+ AttrElement(adm_name="normalization", arg_name="normalization", type=StringType, default="SN3D"),
+ AttrElement(adm_name="nfcRefDist", arg_name="nfcRefDist", type=evolve(FloatType, loads=ref_dist_loads)),
+ AttrElement(adm_name="screenRef", arg_name="screenRef", type=BoolType, default=False),
+])
+
+
+block_format_handlers = {
+ TypeDefinition.Objects: parse_block_format_objects,
+ TypeDefinition.DirectSpeakers: parse_block_format_direct_speakers,
+ TypeDefinition.Binaural: block_format_binaural_handler.parse,
+ TypeDefinition.HOA: block_format_HOA_handler.parse,
+}
+
+
+def handle_block_format(kwargs, el):
+ type = kwargs["type"]
+ try:
+ handler = block_format_handlers[type]
+ except KeyError:
+ raise ValueError("Do not know how to parse block format of type {type.name}".format(type=type))
+ block_format = handler(el)
+ kwargs.setdefault("audioBlockFormats", []).append(block_format)
+
+
+block_format_to_xml_handlers = {
+ TypeDefinition.Objects: block_format_objects_to_xml,
+ TypeDefinition.DirectSpeakers: block_format_direct_speakers_to_xml,
+ TypeDefinition.Binaural: block_format_binaural_handler.to_xml,
+ TypeDefinition.HOA: block_format_HOA_handler.to_xml,
+}
+
+
+def block_format_to_xml(parent, obj):
+ try:
+ handler = block_format_to_xml_handlers[obj.type]
+ except KeyError:
+ raise ValueError("Do not know how to generate block format of type {type.name}".format(type=obj.type))
+ for bf in obj.audioBlockFormats:
+ handler(parent, bf)
+
+
+def handle_frequency(kwargs, el):
+ frequency = kwargs.setdefault("frequency", Frequency())
+
+ type = el.attrib["typeDefinition"]
+ value = FloatType.loads_func(el.text)
+
+ if type == "lowPass":
+ if frequency.lowPass is not None:
+ raise ValueError("More than one lowPass frequency element specified.")
+ frequency.lowPass = value
+ elif type == "highPass":
+ if frequency.highPass is not None:
+ raise ValueError("More than one highPass frequency element specified.")
+ frequency.highPass = value
+ else:
+ raise ValueError("frequency type must be lowPass or highPass, not {type}".format(type=type))
+
+
+def frequency_to_xml(parent, obj):
+ if obj.frequency.lowPass is not None:
+ element = parent.makeelement(QName(default_ns, "frequency"), typeDefinition="lowPass")
+ element.text = FloatType.dumps(obj.frequency.lowPass)
+ parent.append(element)
+ if obj.frequency.highPass is not None:
+ element = parent.makeelement(QName(default_ns, "frequency"), typeDefinition="highPass")
+ element.text = FloatType.dumps(obj.frequency.highPass)
+ 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),
+ type_handler,
+ CustomElement("audioBlockFormat", handle_block_format, arg_name="audioBlockFormats", to_xml=block_format_to_xml, required=True),
+ CustomElement("frequency", handle_frequency, to_xml=frequency_to_xml),
+])
+
+pack_format_handler = ElementParser(AudioPackFormat, "audioPackFormat", [
+ Attribute(adm_name="audioPackFormatID", arg_name="id", required=True),
+ Attribute(adm_name="audioPackFormatName", arg_name="audioPackFormatName", required=True),
+ type_handler,
+ Attribute(adm_name="importance", arg_name="importance", type=IntType),
+ RefList("audioChannelFormat"),
+ RefList("audioPackFormat"),
+ AttrElement(adm_name="absoluteDistance", arg_name="absoluteDistance", type=FloatType),
+])
+
+stream_format_handler = ElementParser(AudioStreamFormat, "audioStreamFormat", [
+ Attribute(adm_name="audioStreamFormatID", arg_name="id", required=True),
+ Attribute(adm_name="audioStreamFormatName", arg_name="audioStreamFormatName", required=True),
+ format_handler,
+ RefList("audioTrackFormat", required=True),
+ RefElement("audioChannelFormat"),
+ RefElement("audioPackFormat"),
+])
+
+track_format_handler = ElementParser(AudioTrackFormat, "audioTrackFormat", [
+ Attribute(adm_name="audioTrackFormatID", arg_name="id", required=True),
+ Attribute(adm_name="audioTrackFormatName", arg_name="audioTrackFormatName", required=True),
+ format_handler,
+ RefElement("audioStreamFormat", parse_only=True),
+])
+
+
+default_screen = PolarScreen(aspectRatio=1.78,
+ centrePosition=PolarPosition(
+ azimuth=0.0,
+ elevation=0.0,
+ distance=1.0),
+ widthAzimuth=58.0)
+
+
+def handle_screen_type(kwargs, screen_type):
+ if "screen_type" in kwargs:
+ if kwargs["screen_type"] != screen_type:
+ raise ValueError("Expected {expected} screen data, got {got}.".format(expected=kwargs["screen_type"], got=screen_type))
+ else:
+ kwargs["screen_type"] = screen_type
+
+
+def handle_centre_position(kwargs, el):
+ keys = set(el.attrib.keys())
+
+ if keys >= set(["X", "Y", "Z"]):
+ kwargs["centrePosition"] = CartesianPosition(X=float(el.attrib["X"]),
+ Y=float(el.attrib["Y"]),
+ Z=float(el.attrib["Z"]))
+ handle_screen_type(kwargs, "cartesian")
+ elif keys >= set(["azimuth", "elevation"]):
+ kwargs["centrePosition"] = PolarPosition(azimuth=float(el.attrib["azimuth"]),
+ elevation=float(el.attrib["elevation"]),
+ distance=float(el.attrib.get("distance", 1.0)))
+ handle_screen_type(kwargs, "polar")
+ else:
+ raise ValueError("Do not know how to parse a screenCentrePosition with keys {}.".format(", ".join(sorted(el.attrib.keys()))))
+
+
+def centre_position_to_xml(parent, obj):
+ pos = obj.centrePosition
+
+ if isinstance(pos, CartesianPosition):
+ parent.append(
+ parent.makeelement(QName(default_ns, "screenCentrePosition"),
+ X=FloatType.dumps(pos.X),
+ Y=FloatType.dumps(pos.Y),
+ Z=FloatType.dumps(pos.Z)))
+ elif isinstance(pos, PolarPosition):
+ parent.append(
+ parent.makeelement(QName(default_ns, "screenCentrePosition"),
+ azimuth=FloatType.dumps(pos.azimuth),
+ elevation=FloatType.dumps(pos.elevation),
+ distance=FloatType.dumps(pos.distance)))
+ else:
+ assert False, "unexpected type" # pragma: no cover
+
+
+def handle_screen_width(kwargs, el):
+ if "X" in el.attrib:
+ kwargs["width"] = float(el.attrib["X"])
+ handle_screen_type(kwargs, "cartesian")
+ elif "azimuth" in el.attrib:
+ kwargs["width"] = float(el.attrib["azimuth"])
+ handle_screen_type(kwargs, "polar")
+ else:
+ raise ValueError("Do not know how to parse a screenWidth with keys {}.".format(", ".join(sorted(el.attrib.keys()))))
+
+
+def screen_width_to_xml(parent, obj):
+ if isinstance(obj, CartesianScreen):
+ parent.append(
+ parent.makeelement(QName(default_ns, "screenWidth"),
+ X=FloatType.dumps(obj.widthX)))
+ elif isinstance(obj, PolarScreen):
+ parent.append(
+ parent.makeelement(QName(default_ns, "screenWidth"),
+ azimuth=FloatType.dumps(obj.widthAzimuth)))
+ else:
+ assert False, "unexpected type" # pragma: no cover
+
+
+def make_screen(aspectRatio, centrePosition, width, screen_type):
+ if screen_type == "cartesian":
+ return CartesianScreen(aspectRatio=aspectRatio,
+ centrePosition=centrePosition,
+ widthX=width)
+ elif screen_type == "polar":
+ return PolarScreen(aspectRatio=aspectRatio,
+ centrePosition=centrePosition,
+ widthAzimuth=width)
+ else:
+ assert False, "unexpected type" # pragma: no cover
+
+
+screen_handler = ElementParser(make_screen, "audioProgrammeReferenceScreen", [
+ AttrElement(adm_name="aspectRatio", arg_name="aspectRatio", type=FloatType, required=True),
+ CustomElement("screenCentrePosition", handle_centre_position, arg_name="centrePosition", to_xml=centre_position_to_xml, required=True),
+ CustomElement("screenWidth", handle_screen_width, arg_name="width", to_xml=screen_width_to_xml, required=True),
+])
+
+
+def make_audio_programme(referenceScreen=None, **kwargs):
+ if referenceScreen is None:
+ referenceScreen = default_screen
+ return AudioProgramme(referenceScreen=referenceScreen, **kwargs)
+
+
+programme_handler = ElementParser(make_audio_programme, "audioProgramme", [
+ Attribute(adm_name="audioProgrammeID", arg_name="id", required=True),
+ Attribute(adm_name="audioProgrammeName", arg_name="audioProgrammeName", required=True),
+ Attribute(adm_name="audioProgrammeLanguage", arg_name="audioProgrammeLanguage"),
+ Attribute(adm_name="start", arg_name="start", type=TimeType),
+ Attribute(adm_name="end", arg_name="end", type=TimeType),
+ Attribute(adm_name="maxDuckingDepth", arg_name="maxDuckingDepth", type=FloatType),
+ RefList("audioContent"),
+ screen_handler.as_handler("referenceScreen", default=default_screen),
+])
+
+content_handler = ElementParser(AudioContent, "audioContent", [
+ Attribute(adm_name="audioContentID", arg_name="id", required=True),
+ Attribute(adm_name="audioContentName", arg_name="audioContentName", required=True),
+ Attribute(adm_name="audioContentLanguage", arg_name="audioContentLanguage"),
+ AttrElement(adm_name="dialogue", arg_name="dialogue", type=IntType),
+ 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),
+ Attribute(adm_name="start", arg_name="start", type=TimeType),
+ Attribute(adm_name="duration", arg_name="duration", type=TimeType),
+ Attribute(adm_name="dialogue", arg_name="dialogue", type=IntType),
+ Attribute(adm_name="importance", arg_name="importance", type=IntType),
+ Attribute(adm_name="interact", arg_name="interact", type=BoolType),
+ Attribute(adm_name="disableDucking", arg_name="disableDucking", type=BoolType),
+ RefList("audioPackFormat"),
+ RefList("audioObject"),
+ RefList("audioComplementaryObject"),
+ ListElement(adm_name="audioTrackUIDRef", arg_name="audioTrackUIDRef", attr_name="audioTrackUIDs", type=RefType),
+])
+
+track_uid_handler = ElementParser(AudioTrackUID, "audioTrackUID", [
+ Attribute(adm_name="UID", arg_name="id", required=True),
+ Attribute(adm_name="sampleRate", arg_name="sampleRate", type=IntType),
+ Attribute(adm_name="bitDepth", arg_name="bitDepth", type=IntType),
+ RefElement("audioTrackFormat"),
+ RefElement("audioPackFormat"),
+])
+
+
+def parse_adm_elements(adm, element, common_definitions=False):
+ element_types = [
+ ("//{ns}audioProgramme", programme_handler.parse, adm.addAudioProgramme),
+ ("//{ns}audioContent", content_handler.parse, adm.addAudioContent),
+ ("//{ns}audioObject", object_handler.parse, adm.addAudioObject),
+ ("//{ns}audioChannelFormat", channel_format_handler.parse, adm.addAudioChannelFormat),
+ ("//{ns}audioPackFormat", pack_format_handler.parse, adm.addAudioPackFormat),
+ ("//{ns}audioStreamFormat", stream_format_handler.parse, adm.addAudioStreamFormat),
+ ("//{ns}audioTrackFormat", track_format_handler.parse, adm.addAudioTrackFormat),
+ ("//{ns}audioTrackUID", track_uid_handler.parse, adm.addAudioTrackUID),
+ ]
+
+ for path, parse_func, add_func in element_types:
+ for sub_element in xpath(element, path):
+ adm_element = parse_func(sub_element)
+
+ if common_definitions:
+ adm_element.is_common_definition = True
+
+ add_func(adm_element)
+
+
+def _sort_block_formats(channelFormats):
+ def sort_key(bf):
+ return (bf.rtime if bf.rtime is not None else Fraction(0),
+ bf.duration if bf.duration is not None else Fraction(0))
+
+ for channelFormat in channelFormats:
+ block_formats = channelFormat.audioBlockFormats
+
+ if any(sort_key(bf_a) > sort_key(bf_b)
+ for bf_a, bf_b in zip(block_formats[:-1], block_formats[1:])):
+ warnings.warn("out of order block formats in {id}".format(id=channelFormat.id))
+ block_formats.sort(key=sort_key)
+
+
+def _set_default_rtimes(channelFormats):
+ """Default rtimes to 0 if a duration is provided.
+
+ This is intended to fix the first block in object based content, where the
+ rtime has sometimes been omitted. Errors with this data, like having
+ multiple blocks with non-zero duration but no rtime will be caught in
+ InterpretTimingMetadata.
+ """
+ for channelFormat in channelFormats:
+ for bf in channelFormat.audioBlockFormats:
+ if bf.rtime is None and bf.duration is not None:
+ bf.rtime = Fraction(0)
+
+
+def _check_block_format_durations(channelFormats, fix=False):
+ for channelFormat in channelFormats:
+ block_formats = channelFormat.audioBlockFormats
+
+ for bf_a, bf_b in zip(block_formats[:-1], block_formats[1:]):
+ if (bf_a.rtime is None or bf_a.duration is None or
+ bf_b.rtime is None or bf_b.duration is None):
+ continue
+
+ old_duration = bf_a.duration
+ new_duration = bf_b.rtime - bf_a.rtime
+
+ if old_duration != new_duration:
+ if fix:
+ warnings.warn("{direction} duration of block format {id}; was: {old}, now: {new}".format(
+ direction="expanded" if new_duration > old_duration else "contracted",
+ id=bf_a.id,
+ old=old_duration,
+ new=new_duration))
+ bf_a.duration = new_duration
+ else:
+ warnings.warn(
+ "(rtime + duration) of block format {id_a} does not equal rtime of block format {id_b}.".format(
+ id_a=bf_a.id,
+ id_b=bf_b.id))
+
+
+def load_axml_doc(adm, element, lookup_references=True, fix_block_format_durations=False):
+ parse_adm_elements(adm, element)
+
+ if lookup_references:
+ adm.lazy_lookup_references()
+
+ _set_default_rtimes(adm.audioChannelFormats)
+ _sort_block_formats(adm.audioChannelFormats)
+ _check_block_format_durations(adm.audioChannelFormats, fix=fix_block_format_durations)
+
+
+def load_axml_string(adm, axmlstr, **kwargs):
+ element = lxml.etree.fromstring(axmlstr)
+ load_axml_doc(adm, element, **kwargs)
+
+
+def load_axml_file(adm, axmlfile, **kwargs):
+ element = lxml.etree.parse(axmlfile)
+ load_axml_doc(adm, element, **kwargs)
+
+
+def parse_string(axmlstr, **kwargs):
+ adm = ADM()
+ from .common_definitions import load_common_definitions
+ load_common_definitions(adm)
+ load_axml_string(adm, axmlstr, **kwargs)
+ return adm
+
+
+def parse_file(axmlfile, **kwargs):
+ adm = ADM()
+ from .common_definitions import load_common_definitions
+ load_common_definitions(adm)
+ load_axml_file(adm, axmlfile, **kwargs)
+ return adm
+
+
+def adm_to_xml(adm):
+ element_types = [
+ (programme_handler.to_xml, adm.audioProgrammes),
+ (content_handler.to_xml, adm.audioContents),
+ (object_handler.to_xml, adm.audioObjects),
+ (channel_format_handler.to_xml, adm.audioChannelFormats),
+ (pack_format_handler.to_xml, adm.audioPackFormats),
+ (stream_format_handler.to_xml, adm.audioStreamFormats),
+ (track_format_handler.to_xml, adm.audioTrackFormats),
+ (track_uid_handler.to_xml, adm.audioTrackUIDs),
+ ]
+
+ E = ElementMaker(namespace=default_ns, nsmap=default_nsmap)
+ afx = E.audioFormatExtended()
+
+ for to_xml, elements in element_types:
+ for element in elements:
+ if not element.is_common_definition:
+ to_xml(afx, element)
+
+ return E.ebuCoreMain(
+ E.coreMetadata(
+ E.format(
+ afx)))
diff --git a/ear/fileio/bw64/__init__.py b/ear/fileio/bw64/__init__.py
new file mode 100644
index 00000000..c185a691
--- /dev/null
+++ b/ear/fileio/bw64/__init__.py
@@ -0,0 +1,2 @@
+from .reader import Bw64Reader # noqa: F401
+from .writer import Bw64Writer # noqa: F401
diff --git a/ear/fileio/bw64/chunks.py b/ear/fileio/bw64/chunks.py
new file mode 100644
index 00000000..da2d717a
--- /dev/null
+++ b/ear/fileio/bw64/chunks.py
@@ -0,0 +1,379 @@
+from attr import attrs, attrib, Factory
+from attr.validators import instance_of, optional
+import struct
+from collections import namedtuple
+from enum import IntEnum
+from six import string_types
+
+ChunkPosition = namedtuple('ChunkPosition', ['chunkId', 'size', 'data', 'end'])
+
+
+class ChunkIndex(object):
+
+ def __init__(self, size, position):
+ self._size = size
+ self._position = ChunkPosition(position,
+ position + 4,
+ position + 8,
+ position + 8 + size)
+
+ @property
+ def size(self):
+ return self._size
+
+ @property
+ def position(self):
+ return self._position
+
+
+class Format(IntEnum):
+ PCM = 1
+ WAVE_FORMAT_IEEE_FLOAT = 3
+ WAVE_FORMAT_EXTENSIBLE = 65534
+
+
+class FormatInfoChunk(object):
+ """ Class representation of the FormatChunk
+
+ This class can be either used to create a new format chunk or simplify
+ reading and validation of a format chunk. Once created the object cannot be
+ changed. You can only read the saved data. The order of the constructor
+ arguments might seem a bit strange at first sight. The order corresponds to
+ the order within a BW64 file. This makes it easier to create an object from
+ the data read from a file. Cumbersome values like bytesPerSecond or
+ blockAlignment can be omitted (thus set to `None`). But: if they are set,
+ they have to be correct. Otherwise a ValueError is raised.
+
+ To simplify the writing of files the FormatChunk (like every Chunk class in
+ this module) has a asByteArray method. This method returns the correct byte
+ array representation of the FormatChunk, which can be directly written to a
+ file.
+ """
+
+ def __init__(self, formatTag=1, channelCount=1, sampleRate=48000,
+ bytesPerSecond=None, blockAlignment=None, bitsPerSample=16,
+ cbSize=None, extraData=None):
+ self._formatTag = int(formatTag)
+ self._channelCount = int(channelCount)
+ self._sampleRate = int(sampleRate)
+ self._bitsPerSample = int(bitsPerSample)
+ self._cbSize = cbSize
+ if(extraData):
+ self._extraData = ExtraData(*extraData)
+ else:
+ self._extraData = None
+
+ if(self.formatTag not in list(Format)):
+ raise ValueError('format not supported: ' + str(self.formatTag))
+
+ if self.cbSize:
+ if cbSize != self.cbSize:
+ raise ValueError(
+ 'sanity check failed. \'cbSize\' is ' +
+ str(cbSize) + ' but should be ' +
+ str(self.cbSize)
+ )
+
+ if(self.formatTag == Format.WAVE_FORMAT_EXTENSIBLE):
+ if not self.extraData:
+ raise RuntimeError(
+ 'missing extra data for WAVE_FORMAT_EXTENSIBLE')
+ if self.extraData.subFormat not in list(Format):
+ raise ValueError(
+ 'subformat not supported: ' + str(self.formatTag))
+ if formatTag != self.extraData.subFormat:
+ raise ValueError(
+ 'sanity check failed. \'formatTag\' and'
+ '\'extraData.subFormat\' do not match.'
+ )
+
+ if(self.channelCount < 1):
+ raise ValueError('channelCount < 1')
+
+ if(self.sampleRate < 1):
+ raise ValueError('sampleRate < 1')
+
+ if(self.bitsPerSample not in [16, 24, 32]):
+ raise ValueError('bit depth not supported: ' +
+ str(self._bitsPerSample))
+
+ if(bytesPerSecond):
+ if(int(bytesPerSecond) != self.bytesPerSecond):
+ raise ValueError(
+ 'sanity check failed. \'bytesPerSecond\' is ' +
+ str(bytesPerSecond) + ' but should be ' +
+ str(self.bytesPerSecond)
+ )
+
+ if(blockAlignment):
+ if(int(blockAlignment) != self.blockAlignment):
+ raise ValueError(
+ 'sanity check failed. \'blockAlignment\' is ' +
+ str(blockAlignment) + ' but should be ' +
+ str(self.blockAlignment)
+ )
+ if(cbSize):
+ if(int(cbSize) != self.cbSize):
+ raise ValueError(
+ 'sanity check failed. \'cbSize\' is ' +
+ str(cbSize) + ' but should be ' +
+ str(self.cbSize)
+ )
+
+ @property
+ def formatTag(self):
+ if(self.extraData):
+ return self.extraData.subFormat
+ else:
+ return self._formatTag
+
+ @property
+ def channelCount(self):
+ return self._channelCount
+
+ @property
+ def sampleRate(self):
+ return self._sampleRate
+
+ @property
+ def bytesPerSecond(self):
+ return self.sampleRate * self.blockAlignment
+
+ @property
+ def blockAlignment(self):
+ return int(self.channelCount * self.bitsPerSample / 8)
+
+ @property
+ def bitsPerSample(self):
+ return self._bitsPerSample
+
+ @property
+ def cbSize(self):
+ if(self.extraData):
+ return len(self.extraData.asByteArray())
+ else:
+ return 0
+
+ @property
+ def extraData(self):
+ return self._extraData
+
+ def asByteArray(self):
+ byteArrayData = struct.pack(' chunkIndex.position.end):
+ self._buffer.seek(chunkIndex.end)
+ else:
+ self._buffer.seek(dataChunkOffset + frameOffset)
+
+ def read(self, numberOfFrames):
+ if(self.tell() + numberOfFrames > len(self)):
+ numberOfFrames = len(self) - self.tell()
+ rawData = self._buffer.read(
+ numberOfFrames * self._formatInfo.blockAlignment)
+ samplesDecoded = decode_pcm_samples(rawData, self.bitdepth)
+ return deinterleave(samplesDecoded, self.channels)
+
+ def tell(self):
+ return ((self._buffer.tell() - self._chunks[b'data'].position.data) //
+ self._formatInfo.blockAlignment)
+
+ def __len__(self):
+ """ Returns number of frames """
+ if (self._ds64):
+ return self._ds64.dataSize // self._formatInfo.blockAlignment
+ else:
+ return self._chunks[b'data'].size // self._formatInfo.blockAlignment
+
+ def _read_riff_chunk(self):
+ chunkId, chunkSize = struct.unpack('<4sI', self._buffer.read(8))
+ self.fileFormat = chunkId
+ self._riffSize = chunkSize
+ if self.fileFormat not in [b'RIFF', b'RF64', b'BW64']:
+ raise RuntimeError('not a riff, rf64 or bw64 file')
+ """
+ # standard conform files should set the chunkSize to -1. But e. g.
+ # Reaper writes the actual size if the output format option "Force
+ # RF64" is selected. Hence we do not check this.
+ if self.fileFormat in [b'RF64', b'BW64']:
+ if self._riffSize != int("0xFFFFFFFF", 0):
+ raise RuntimeError(
+ 'malformed rf64 or bw64 file: chunkSize != -1')
+ """
+ riffType = struct.unpack('<4s', self._buffer.read(4))[0]
+ if riffType != b'WAVE':
+ raise RuntimeError('not a wave file')
+
+ def _read_ds64_chunk(self):
+ chunkId, chunkSize = struct.unpack('<4sI', self._buffer.read(8))
+ if chunkId != b'ds64':
+ raise RuntimeError(
+ 'malformed rf64 or bw64 file: missing ds64 chunk')
+ chunkData = self._buffer.read(chunkSize)
+
+ fixedPartFmt = '<3QI'
+ fixedPartSize = struct.calcsize(fixedPartFmt)
+ tableEntryFmt = '<4sQ'
+ TableEntrySize = struct.calcsize(tableEntryFmt)
+
+ fixedPart, tablePart = chunkData[:fixedPartSize], chunkData[fixedPartSize:]
+
+ riffSize, dataSize, dummy, tableLength = struct.unpack(fixedPartFmt, fixedPart)
+ self._ds64 = DataSize64Chunk(riffSize, dataSize, dummy)
+
+ for tableId in range(tableLength):
+ tableStart = tableId * TableEntrySize
+ tableEntryPart = tablePart[tableStart: tableStart + TableEntrySize]
+
+ chunkId, chunkSize = struct.unpack(tableEntryFmt, tableEntryPart)
+ self._ds64.addTableEntry(chunkId, chunkSize)
+
+ def _read_chunk_header(self):
+ data = self._buffer.read(8)
+ if len(data) != 8: # EOF
+ return None
+ chunkId, chunkSize = struct.unpack('<4sI', data)
+ # correct chunkSize for rf64 and bw64 files
+ if self.fileFormat in [b'RF64', b'BW64']:
+ if chunkId == b'data':
+ chunkSize = self._ds64.dataSize
+ elif chunkId in self._ds64.table:
+ chunkSize = self._ds64.table[chunkId]
+ return (chunkId, chunkSize)
+
+ def _read_chunks(self):
+ while True:
+ chunkHeader = self._read_chunk_header()
+ if not chunkHeader:
+ return
+ chunkId = chunkHeader[0]
+ chunkSize = chunkHeader[1]
+ self._chunks[chunkId] = ChunkIndex(
+ chunkSize, self._buffer.tell() - 8)
+ # always skip an even number of bytes
+ self._buffer.seek(chunkSize + (chunkSize & 1), 1)
+
+ def _read_fmt_chunk(self):
+ last_position = self._buffer.tell()
+ self._buffer.seek(self._chunks[b'fmt '].position.data)
+ if(self._chunks[b'fmt '].size == 16):
+ formatInfo = struct.unpack(' 2**23 - 1] = (
+ decodedSamples[decodedSamples > 2**23 - 1] - 2**24
+ )
+ elif(bitdepth == 32):
+ decodedSamples = np.frombuffer(samples, dtype='int32')
+ else:
+ raise RuntimeError('unsupported bitdepth')
+ return decodedSamples / float((2**(bitdepth - 1) - 1))
+
+
+def encode_pcm_samples(samples, bitdepth):
+ samples = np.array(samples)
+ samples[samples > 1.0] = 1.0
+ samples[samples < -1.0] = -1.0
+ scaledSamples = samples * (2**(bitdepth - 1) - 1)
+ if(bitdepth == 16):
+ encodedSamples = scaledSamples.astype('int16').tobytes()
+ elif(bitdepth == 24):
+ encodedSamples = scaledSamples.astype('int32').tobytes()
+ encodedSamples = bytearray(encodedSamples)
+ del encodedSamples[3::4]
+ elif(bitdepth == 32):
+ encodedSamples = scaledSamples.astype('int32').tobytes()
+ else:
+ raise RuntimeError('unsupported bitdepth')
+ return encodedSamples
diff --git a/ear/fileio/bw64/writer.py b/ear/fileio/bw64/writer.py
new file mode 100644
index 00000000..abc1396e
--- /dev/null
+++ b/ear/fileio/bw64/writer.py
@@ -0,0 +1,235 @@
+import struct
+import numpy as np
+from .chunks import ChunkIndex, FormatInfoChunk, DataSize64Chunk
+from .utils import interleave, encode_pcm_samples
+
+
+class Bw64Writer(object):
+
+ def __init__(self, buffer, formatInfo=FormatInfoChunk(), chna=None,
+ axml=None, bext=None, forceBw64=False):
+ """Write / create a new bw64 file to buffer.
+
+ File format will be setup according to the specified formatinfo. Only
+ PCM data (16bit, 24bit, 32bit) is currently supported.
+
+ If axml data is given on construction, it will be written to the BW64
+ file immediatly. If this is not possible on construction, one can use
+ `set_axml` to set the axml data. In this case, it will be written
+ _after_ the `data` chunk when the file is closed.
+
+ After creation, sample data can be written to the `data` using `write`.
+ The file will be finalized by calling `close()`.
+
+ Note
+ ----
+ If you forget to `close()` the output object, the resulting file will
+ be corrupted.
+
+ Parameters
+ ----------
+ buffer: file - like object
+ formatinfo: FormatInfoChunk
+ Target format of the BW64 file.
+ axml: str
+ Content for the axml chunk.
+ """
+ self._buffer = buffer
+ self._chunks = {}
+ self._dataBytesWritten = 0
+ self._formatInfo = formatInfo
+ self._chnaChunkWritten = False
+ self._chna = chna
+ self._axmlChunkWritten = False
+ self._axml = axml
+ self._bextChunkWritten = False
+ self._bext = bext
+ self._forceBw64 = forceBw64
+
+ self._buffer.seek(0)
+ self._write_riff_chunk()
+ self._write_junk_chunk()
+ self._write_fmt_chunk()
+ if(chna):
+ self._write_chna_chunk()
+ if(axml):
+ self._write_axml_chunk()
+ if(bext):
+ self._write_bext_chunk()
+ self._write_data_chunk_header()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+ self._buffer.close()
+
+ @property
+ def channels(self):
+ return self._formatInfo.channelCount
+
+ @property
+ def sampleRate(self):
+ return self._formatInfo.sampleRate
+
+ @property
+ def bitdepth(self):
+ return self._formatInfo.bitsPerSample
+
+ @property
+ def chna(self):
+ return self._chna
+
+ @chna.setter
+ def chna(self, chna):
+ self._chna = chna
+
+ @property
+ def axml(self):
+ return self._axml
+
+ @axml.setter
+ def axml(self, axml):
+ self._axml = axml
+
+ @property
+ def bext(self):
+ return self._bext
+
+ @bext.setter
+ def bext(self, bext):
+ self._bext = bext
+
+ def write(self, samples):
+ """Append sample data to the BW64 data chunk.
+
+ Parameters
+ ----------
+ samples: array - like, dtype float
+ Array of audio samples, columns correspond to channels
+ Expects float sample values in the range(-1, 1).
+ """
+ assert np.array(samples).shape[1] == self.channels
+
+ samplesInterleaved = interleave(samples)
+ samplesEncoded = encode_pcm_samples(samplesInterleaved, self.bitdepth)
+ self._buffer.write(samplesEncoded)
+ self._dataBytesWritten += len(samplesEncoded)
+
+ def close(self):
+ """Close and finalize the BW64 output.
+
+ This means that the final chunk sizes will be written to the buffer.
+
+ If you forget to call this function, the resulting file will be
+ corrupted. Thus, it might be a good idea to use this with a
+ contextmanager.
+ """
+ if not self._chnaChunkWritten and self._chna:
+ self._write_chna_chunk()
+ if not self._axmlChunkWritten and self._axml:
+ self._write_axml_chunk()
+ if not self._bextChunkWritten and self._bext:
+ self._write_bext_chunk()
+
+ riffChunkSize = self._calc_riff_chunk_size()
+ if(riffChunkSize >= 2**32) or self._forceBw64:
+ self._update_bw64_chunk()
+ self._overwrite_junk_with_ds64_chunk()
+ else:
+ self._update_riff_chunk_size()
+ self._update_data_chunk_size()
+
+ def _write_riff_chunk(self):
+ self._buffer.write(b'RIFF')
+ self._buffer.write(b'\xff\xff\xff\xff')
+ self._buffer.write(b'WAVE')
+
+ def _write_data_chunk_header(self):
+ self._chunks[b'data'] = ChunkIndex(-1, self._buffer.tell())
+ self._buffer.write(b'data')
+ self._buffer.write(b'\xff\xff\xff\xff')
+
+ def _write_fmt_chunk(self):
+ fmtChunkData = self._formatInfo.asByteArray()
+ self._chunks[b'fmt '] = ChunkIndex(
+ len(fmtChunkData), self._buffer.tell())
+ self._buffer.write(fmtChunkData)
+
+ def _calc_riff_chunk_size(self):
+ last_position = self._buffer.tell()
+ self._buffer.seek(0, 2)
+ riffChunkSize = self._buffer.tell() - 8
+ self._buffer.seek(last_position)
+ return riffChunkSize
+
+ def _update_riff_chunk_size(self):
+ riffChunkSize = self._calc_riff_chunk_size()
+ last_position = self._buffer.tell()
+ self._buffer.seek(4)
+ self._buffer.write(struct.pack(' '.join(o.id for o in path)))
+ path = path + (pack_format,)
+
+ current_pack_importance = _update_importance_value(current_pack_importance, pack_format.importance)
+
+ yield pack_format, current_pack_importance
+
+ for direct_sub_pack in pack_format.audioPackFormats:
+ for sub_pack, sub_importance in _recurse_audio_pack(direct_sub_pack, path, current_pack_importance):
+ yield sub_pack, sub_importance
+
+
+def _calulate_pack_importance(target_pack_format, parent_audio_object):
+ """
+ Calculates the effective importance for the `target_pack_format`, taking
+ into account all potential parent audio_packs starting from `parent_audio_object`.
+ """
+ if parent_audio_object is None:
+ return target_pack_format.importance
+ for pack, importance in _recurse_audio_packs(parent_audio_object.audioPackFormats):
+ if pack.id == target_pack_format.id:
+ return importance
+ return None
+
+
+class RenderingItemHandler():
+
+ def __init__(self, adm, audio_programme_id=None, selected_complementary_objects=None):
+ self._adm = adm
+ self._audio_programme_id = audio_programme_id
+ if selected_complementary_objects is not None:
+ self._selected_complementary_objects = selected_complementary_objects
+ else:
+ self._selected_complementary_objects = {}
+ self._ignore_object_list = []
+ self._map_track_format_to_stream_format()
+ self._select_complementary_objects()
+ self.selected_items = self._select_rendering_items()
+
+ def _select_programme(self):
+ """Select the audioProgramme"""
+ if self._audio_programme_id is None:
+ if len(self._adm.audioProgrammes) > 1:
+ warnings.warn("more than one audioProgramme; selecting the one with the lowest id")
+ return sorted(self._adm.audioProgrammes, key=lambda programme: programme.id)[0]
+
+ for programme in self._adm.audioProgrammes:
+ if programme.id == self._audio_programme_id:
+ return programme
+
+ def _root_objects(self):
+ """Get all objects which are not sub-objects of another object."""
+ non_root_objects = set(id(subObject)
+ for audioObject in self._adm.audioObjects
+ for subObject in audioObject.audioObjects)
+
+ for audioObject in self._adm.audioObjects:
+ if id(audioObject) not in non_root_objects:
+ yield audioObject
+
+ def _select_objects(self):
+ """Get objects (and their associated reference screens) to render,
+ either via the programmes or by selecting all objects.
+
+ yields:
+ AudioObject
+ AudioProgramme.CartesianReferenceScreen or AudioProgramme.PolarReferenceScreen
+ """
+ if self._adm.audioProgrammes:
+ audioProgramme = self._select_programme()
+ for audioContent in audioProgramme.audioContents:
+ for audioObject in audioContent.audioObjects:
+ for subObject, object_importance in self._sub_objects(audioObject):
+ if subObject not in self._ignore_object_list:
+ yield subObject, audioProgramme.referenceScreen, object_importance
+ else:
+ for audioObject in self._root_objects():
+ for subObject, object_importance in self._sub_objects(audioObject):
+ if subObject not in self._ignore_object_list:
+ yield subObject, default_screen, object_importance
+
+ def _sub_objects(self, audio_object, path=(), current_object_importance=None):
+ """Yield all audioObjects referenced by a given audioObject, including itself."""
+ if _in_by_id(audio_object, path):
+ raise AdmError("loop detected in audioObjects: {path}".format(path=' -> '.join(o.id for o in path)))
+ path = path + (audio_object,)
+
+ current_object_importance = _update_importance_value(current_object_importance, audio_object.importance)
+
+ yield audio_object, current_object_importance
+
+ for direct_sub_object in audio_object.audioObjects:
+ for sub_object, sub_importance in self._sub_objects(direct_sub_object, path, current_object_importance):
+ yield sub_object, sub_importance
+
+ def _ignore_object(self, audio_object):
+ for sub_object, object_importance in self._sub_objects(audio_object):
+ self._ignore_object_list.append(sub_object)
+
+ def _complementary_audio_object_group(self, audio_object):
+ yield audio_object
+ for comp_audio_object in audio_object.audioComplementaryObjects:
+ yield comp_audio_object
+
+ def _select_complementary_objects(self):
+ for audio_object in self._adm.audioObjects:
+ selected_comp_audio_object = self._selected_complementary_objects.get(audio_object.id,
+ audio_object.id)
+ for comp_audio_object in self._complementary_audio_object_group(audio_object):
+ if comp_audio_object.id != selected_comp_audio_object:
+ self._ignore_object(comp_audio_object)
+
+ def _select_tracks(self):
+ """Get selected tracks with information from elsewhere in the ADM.
+
+ Either selects tracks from audioObjects if there is a
+ programme/content/object structure (see _select_objects), or just
+ selects all tracks; this is intended to handle files without an AXML
+ chunk.
+
+ Yields:
+ AudioTrackUID -- track to render
+ ExtraData -- populated with object times, and reference screen
+ AudioObject -- if this track was found inside an audio object
+ """
+ if self._adm.audioObjects or self._adm.audioProgrammes:
+ for audioObject, referenceScreen, object_importance in self._select_objects():
+ for audio_track_uid in audioObject.audioTrackUIDs:
+ extra_data = ExtraData(object_start=audioObject.start,
+ object_duration=audioObject.duration,
+ reference_screen=referenceScreen)
+ yield audio_track_uid, extra_data, audioObject, object_importance
+ else:
+ for audio_track_uid in self._adm.audioTrackUIDs:
+ yield audio_track_uid, ExtraData(), None, None
+
+ def _channels_in_pack_format(self, pack_format):
+ channels = []
+
+ for nested_pack_format in pack_format.audioPackFormats:
+ channels.extend(self._channels_in_pack_format(nested_pack_format))
+
+ channels.extend(pack_format.audioChannelFormats)
+
+ return channels
+
+ def _map_track_format_to_stream_format(self):
+ """Map audioStreamFormat to audioTrackFormat references."""
+ self._atf_to_asf_mapping = {}
+ for stream in self._adm.audioStreamFormats:
+ for track in stream.audioTrackFormats:
+ if id(track) in self._atf_to_asf_mapping:
+ raise AdmError("don't know how to handle audioTrackFormat linked to by multiple audioStreamFormats")
+ self._atf_to_asf_mapping[id(track)] = stream
+
+ def _check_consistency(self, audio_object, audio_track_uid):
+ """Check consistency"""
+
+ track_format = audio_track_uid.audioTrackFormat
+ if id(track_format) not in self._atf_to_asf_mapping:
+ raise AdmError("audioTrackUID '{atu.id}' references audioTrackFormat '{atf.id}', which is not referenced by any audioStreamFormat".format(
+ atu=audio_track_uid,
+ atf=track_format)
+ )
+
+ stream_format = self._atf_to_asf_mapping[id(track_format)]
+ channel_format = stream_format.audioChannelFormat
+ if channel_format is None:
+ raise AdmError("no audioChannelFormat linked from audioStreamFormat {asf.id}".format(asf=stream_format))
+ if audio_track_uid.trackIndex is None:
+ raise AdmError("audioTrackUID {atu.id} does not have a track index, "
+ "which should be specified in the CHNA chunk".format(atu=audio_track_uid))
+
+ if audio_track_uid.audioPackFormat is not None:
+ if audio_object is not None and audio_object.audioPackFormats:
+ unravelled_audio_packs = [pack for pack, importance in _recurse_audio_packs(audio_object.audioPackFormats)]
+ if not _in_by_id(audio_track_uid.audioPackFormat, unravelled_audio_packs):
+ raise AdmError("audioObject {ao.id} does not reference audioPackFormat {apf.id} "
+ "which is referenced by audioTrackUID {atu.id}".format(
+ ao=audio_object,
+ apf=audio_track_uid.audioPackFormat,
+ atu=audio_track_uid))
+ if not _in_by_id(channel_format, self._channels_in_pack_format(audio_track_uid.audioPackFormat)):
+ raise AdmError("audioPackFormat {apf.id} does not reference audioChannelFormat {acf.id} "
+ "which is referenced by audioTrackUID {atu.id}".format(
+ apf=audio_track_uid.audioPackFormat,
+ acf=channel_format,
+ atu=audio_track_uid))
+
+ def _select_rendering_items(self):
+ """Select items to render.
+
+ The items are selected by following the references from an
+ audioProgramme to every individual audioChannelFormat
+
+ audioProgramme
+ -> audioContent
+ -> audioObject
+ -> audioTrackUID
+ -> audioTrackFormat
+ -> audioStreamFormat
+ -> audioChannelFormat
+
+ If there is no audioProgramme within the ADM file, the collection of all
+ audioObjects will be used as the starting point.
+
+ Returns:
+ list of RenderingItem
+ """
+ hoa_channels_by_object_pack = defaultdict(list)
+ items = []
+
+ for audio_track_uid, extra_data, audio_object, object_importance in self._select_tracks():
+ self._check_consistency(audio_object, audio_track_uid)
+
+ stream_format = self._atf_to_asf_mapping[id(audio_track_uid.audioTrackFormat)]
+ channel_format = stream_format.audioChannelFormat
+ extra_data.channel_frequency = channel_format.frequency
+ pack_importance = _calulate_pack_importance(audio_track_uid.audioPackFormat, audio_object)
+ importance = ImportanceData(audio_object=object_importance, audio_pack_format=pack_importance)
+
+ if channel_format.type == TypeDefinition.Objects:
+ metadata_source = MetadataSourceIter([ObjectTypeMetadata(block_format=block_format,
+ extra_data=extra_data)
+ for block_format in channel_format.audioBlockFormats])
+
+ items.append(ObjectRenderingItem(track_index=audio_track_uid.trackIndex - 1,
+ metadata_source=metadata_source,
+ importance=importance))
+ elif channel_format.type == TypeDefinition.DirectSpeakers:
+ metadata_source = MetadataSourceIter([DirectSpeakersTypeMetadata(block_format=block_format,
+ extra_data=extra_data)
+ for block_format in channel_format.audioBlockFormats])
+
+ items.append(DirectSpeakersRenderingItem(track_index=audio_track_uid.trackIndex - 1,
+ metadata_source=metadata_source,
+ importance=importance))
+ elif channel_format.type == TypeDefinition.HOA:
+ if audio_track_uid.audioPackFormat is None:
+ raise AdmError("HOA track UIDs must have a pack format")
+
+ hoa_channels_by_object_pack[(id(audio_object), id(audio_track_uid.audioPackFormat))].append((audio_track_uid,
+ channel_format,
+ extra_data,
+ importance))
+ else:
+ raise Exception("Don't know how to produce rendering items for type {type}".format(type=channel_format.type))
+
+ for channels in hoa_channels_by_object_pack.values():
+ items.append(self._hoa_channels_to_rendering_item(channels))
+
+ return items
+
+ def _hoa_channels_to_rendering_item(self, channels):
+ """Turn a list of associated HOA channels into a rendering item.
+
+ Args:
+ channels (list): list of tuples:
+ - audioTrackUID
+ - audioChannelFormat
+ - ExtraData
+
+ Returns:
+ TypeMetadata
+ """
+ audio_track_uid, channel_format, reference_extra_data, reference_importance = channels[0]
+ [reference_block_format] = channel_format.audioBlockFormats
+
+ orders = []
+ degrees = []
+ track_indices = []
+
+ for audio_track_uid, channel_format, extra_data, importance in channels:
+ [block_format] = channel_format.audioBlockFormats
+
+ if (block_format.order, block_format.degree) in zip(orders, degrees):
+ raise AdmError("Duplicate HOA orders and degrees found; see {audio_track_uid.id}.".format(audio_track_uid=audio_track_uid))
+
+ if audio_track_uid.trackIndex - 1 in track_indices:
+ raise AdmError("Duplicate tracks used in HOA; see {track.id}.".format(track=audio_track_uid))
+
+ orders.append(block_format.order)
+ degrees.append(block_format.degree)
+ track_indices.append(audio_track_uid.trackIndex - 1)
+
+ # other block and channel attributes must match
+ if block_format.normalization != reference_block_format.normalization:
+ raise AdmError("All HOA channel formats in a single pack format must have the same normalization.")
+ if block_format.nfcRefDist != reference_block_format.nfcRefDist:
+ raise AdmError("All HOA channel formats in a single pack format must have the same nfcRefDist.")
+ if block_format.screenRef != reference_block_format.screenRef:
+ raise AdmError("All HOA channel formats in a single pack format must have the same screenRef.")
+ if extra_data.channel_frequency != reference_extra_data.channel_frequency:
+ raise AdmError("All HOA channel formats in a single pack format must have the same frequency.")
+
+ if block_format.rtime != reference_block_format.rtime:
+ raise AdmError("All HOA block formats must not have same rtime.")
+ if block_format.duration != reference_block_format.duration:
+ raise AdmError("All HOA block formats must not have same duration.")
+
+ assert extra_data == reference_extra_data
+ assert importance == reference_importance
+
+ # check that orders/degrees/indices? are unique
+ if any(count != 1 for count in Counter(zip(orders, degrees)).values()):
+ raise AdmError("Duplicated orders and degrees in HOA.")
+ if any(count != 1 for count in Counter(track_indices).values()):
+ raise AdmError("Duplicated track indices in HOA.")
+
+ type_metadata = HOATypeMetadata(
+ rtime=reference_block_format.rtime,
+ duration=reference_block_format.duration,
+ orders=orders,
+ degrees=degrees,
+ normalization=reference_block_format.normalization,
+ nfcRefDist=reference_block_format.nfcRefDist,
+ screenRef=reference_block_format.screenRef,
+ extra_data=reference_extra_data,
+ )
+ metadata_source = MetadataSourceIter([type_metadata])
+ return HOARenderingItem(track_indices=track_indices,
+ metadata_source=metadata_source,
+ importance=reference_importance)
diff --git a/ear/options.py b/ear/options.py
new file mode 100644
index 00000000..93a1fc2e
--- /dev/null
+++ b/ear/options.py
@@ -0,0 +1,131 @@
+from attr import attrs, attrib
+from ruamel import yaml
+from functools import wraps
+import warnings
+
+
+@attrs
+class Option(object):
+ """Objects representing a single defaulted option to be passed to a
+ function.
+
+ Parameters:
+ default (any type): default value for this option
+ description (string): description for this option
+ """
+ default = attrib()
+ description = attrib()
+
+ def _get_default_yaml(self, *args, **kwargs):
+ return self.default
+
+
+@attrs
+class SubOptions(object):
+ """Objects representing a group of options that will be delegated to some
+ other function.
+
+ Parameters:
+ handler (OptionsHandler): options supported by the function that this
+ option will be delegated to
+ description (string): description for this option
+ """
+ handler = attrib()
+ description = attrib()
+
+ @property
+ def default(self):
+ return {}
+
+ def _get_default_yaml(self, *args, **kwargs):
+ return self.handler._get_defaults_yaml(*args, **kwargs)
+
+
+class OptionsHandler(object):
+ """Objects that represent a set of defaulted options that can be passed to
+ a function.
+
+ Parameters:
+ **options: keys represent option names; values may be either Option or
+ SubOptions instances
+ """
+ def __init__(self, **options):
+ self.options = options
+
+ def set_defaults(self, options):
+ """add default values to options
+
+ Parameters:
+ options (dict): dictionary that defaults are to be added to
+ """
+ for key, option in self.options.items():
+ if key not in options:
+ options[key] = option.default
+
+ def with_defaults(self, f):
+ """Decorate f, such that the default options are added to the keyword arguments."""
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ self.set_defaults(kwargs)
+ return f(*args, **kwargs)
+ return wrapper
+
+ def _get_defaults_yaml(self, indent=2, current_indent=0):
+ mapping = yaml.comments.CommentedMap()
+ for key, option in self.options.items():
+ mapping[key] = option._get_default_yaml(indent=indent, current_indent=current_indent + indent)
+ mapping.yaml_set_comment_before_after_key(key, option.description, indent=current_indent)
+
+ return mapping
+
+
+def _merge_options_into_defaults(options, defaults):
+ for key, option in options.items():
+ if key in defaults:
+ if isinstance(defaults[key], yaml.comments.CommentedMap):
+ _merge_options_into_defaults(option, defaults[key])
+ else:
+ defaults[key] = option
+ else:
+ warnings.warn("removing unknown option {}".format(key))
+
+
+def dump_config_with_comments(root_handler, options={}, indent=4):
+ """Dump a configuration to a yaml-formatted string, including the default
+ options, and optionally user-set options, and comments with option
+ descriptions.
+
+ Parameters:
+ root_handler (OptionsHandler): handler to get defaults and descriptions from
+ options (dict): user-provided options that override the defaults in root_handler
+ indent (int): indent size in spaces
+ """
+ defaults = root_handler._get_defaults_yaml(indent=indent)
+
+ _merge_options_into_defaults(options, defaults)
+
+ return yaml.dump(defaults,
+ Dumper=yaml.RoundTripDumper,
+ indent=indent)
+
+
+def test_merge():
+ options_a = OptionsHandler(
+ foo=Option(5, "foo"),
+ bar=Option(6, "bar"),
+ )
+ options_b = OptionsHandler(
+ a_opts=SubOptions(options_a, "options for a"),
+ baz=Option(4, "baz"),
+ )
+
+ options = dict(
+ a_opts=dict(
+ foo=8)
+ )
+
+ defaults = options_b._get_defaults_yaml()
+ _merge_options_into_defaults(options, defaults)
+ assert defaults["a_opts"]["foo"] == 8
+ assert defaults["a_opts"]["bar"] == 6
+ assert defaults["baz"] == 4
diff --git a/ear/test/__init__.py b/ear/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ear/test/data/test.wav b/ear/test/data/test.wav
new file mode 100644
index 00000000..668f9e82
Binary files /dev/null and b/ear/test/data/test.wav differ
diff --git a/ear/test/data/test.yaml b/ear/test/data/test.yaml
new file mode 100644
index 00000000..eda7615f
--- /dev/null
+++ b/ear/test/data/test.yaml
@@ -0,0 +1,56 @@
+name: front centre + top left
+items:
+ - type: "Objects"
+ channels: [1]
+ blocks:
+ - rtime: 0.0
+ duration: 1/8
+ position:
+ azimuth: 0.0
+ elevation: 0.0
+ distance: 1.0
+ gain: 1.0
+ - rtime: 1/8
+ duration: 1/8
+ position:
+ azimuth: -30.0
+ elevation: 0.0
+ distance: 1.0
+ gain: 1.0
+ jumpPosition: {flag: true, interpolationLength: 0.0}
+ - type: "Objects"
+ channels: [2]
+ blocks:
+ - rtime: 0.0
+ duration: 1/4
+ position:
+ azimuth: 30.0
+ elevation: 30.0
+ distance: 1.0
+ gain: 1.0
+ - type: "DirectSpeakers"
+ channels: [3]
+ blocks:
+ - speakerLabel: ["U-030"]
+ position:
+ azimuth: -30.0
+ elevation: 30.0
+ - type: "DirectSpeakers"
+ channels: [3]
+ blocks:
+ - speakerLabel: ["M+045"]
+ position:
+ azimuth:
+ value: 45.0
+ min: 30.0
+ max: 50.0
+ elevation: 0.0
+ - type: "DirectSpeakers"
+ channels: [4]
+ frequency:
+ lowPass: 120.0
+ blocks:
+ - speakerLabel: ["LFE1"]
+ position:
+ azimuth: 0.0
+ elevation: -30.0
diff --git a/ear/test/data/test_adapt_meta.yaml b/ear/test/data/test_adapt_meta.yaml
new file mode 100644
index 00000000..4803050d
--- /dev/null
+++ b/ear/test/data/test_adapt_meta.yaml
@@ -0,0 +1,12 @@
+name: front centre + top left
+items:
+ - type: "Objects"
+ channels: [1]
+ blocks:
+ - rtime: 0.0
+ duration: 1/4
+ position:
+ azimuth: 90.0
+ elevation: 0.0
+ distance: 1.0
+ gain: 1.0
diff --git a/ear/test/data/test_adapt_speakers.yaml b/ear/test/data/test_adapt_speakers.yaml
new file mode 100644
index 00000000..7140c503
--- /dev/null
+++ b/ear/test/data/test_adapt_speakers.yaml
@@ -0,0 +1,19 @@
+speakers:
+- channel: 0
+ names: M+030
+ position: {az: 30.0, el: 0.0, r: 1.0}
+- channel: 1
+ names: M-030
+ position: {az: -30.0, el: 0.0, r: 1.0}
+- channel: 2
+ names: M+000
+ position: {az: 0.0, el: 0.0, r: 1.0}
+- channel: 3
+ names: LFE1
+ position: {az: 45.0, el: -30.0, r: 1.0}
+- channel: 4
+ names: M-110
+ position: {az: -90.0, el: 0.0, r: 1.0}
+- channel: 6
+ names: M+110
+ position: {az: 90.0, el: 0.0, r: 1.0}
diff --git a/ear/test/data/test_bwf.wav b/ear/test/data/test_bwf.wav
new file mode 100644
index 00000000..0c2b639d
Binary files /dev/null and b/ear/test/data/test_bwf.wav differ
diff --git a/ear/test/test_common.py b/ear/test/test_common.py
new file mode 100644
index 00000000..da88ebfa
--- /dev/null
+++ b/ear/test/test_common.py
@@ -0,0 +1,24 @@
+from ..common import cart, PolarPosition
+import numpy.testing as npt
+import numpy as np
+
+
+def test_PolarPosition():
+ # check that each attribute is passed through and converted to float
+ pos = PolarPosition(10, 11, 1)
+ assert pos.azimuth == 10 and isinstance(pos.azimuth, float)
+ assert pos.elevation == 11 and isinstance(pos.elevation, float)
+ assert pos.distance == 1 and isinstance(pos.distance, float)
+
+ # should have the same behaviour as cart (tested below)
+ for az, el in [(0, 0), (0, 30), (30, 0)]:
+ npt.assert_allclose(PolarPosition(az, el, 1).as_cartesian_array(), cart(az, el, 1))
+ npt.assert_allclose(PolarPosition(az, el, 2).as_cartesian_array(), cart(az, el, 2))
+ npt.assert_allclose(PolarPosition(az, el, 2).norm_position, cart(az, el, 1))
+
+
+def test_cart():
+ npt.assert_allclose(cart(0.0, 0.0, 1.0), np.array([0.0, 1.0, 0.0]))
+ npt.assert_allclose(cart(0.0, 0.0, 2.0), np.array([0.0, 2.0, 0.0]))
+ npt.assert_allclose(cart(45.0, 0.0, np.sqrt(2)), np.array([-1.0, 1.0, 0.0]))
+ npt.assert_allclose(cart(0.0, 45.0, np.sqrt(2)), np.array([0.0, 1.0, 1.0]))
diff --git a/ear/test/test_integrate.py b/ear/test/test_integrate.py
new file mode 100644
index 00000000..5e254738
--- /dev/null
+++ b/ear/test/test_integrate.py
@@ -0,0 +1,178 @@
+import numpy as np
+import numpy.testing as npt
+import soundfile
+import os.path
+import subprocess
+import pytest
+
+files_dir = os.path.join(os.path.dirname(__file__), "data")
+
+wav_file = os.path.join(files_dir, "test.wav")
+meta_file = os.path.join(files_dir, "test.yaml")
+bwf_file = os.path.join(files_dir, "test_bwf.wav")
+
+sr = 48000
+
+
+def generate_samples():
+ n_samp = sr // 4
+ return np.array([np.sin(np.arange(n_samp) * 1000 * 2 * np.pi / sr),
+ np.sin(np.arange(n_samp) * 500 * 2 * np.pi / sr),
+ np.sin(np.arange(n_samp) * 2000 * 2 * np.pi / sr),
+ np.sin(np.arange(n_samp) * 50 * 2 * np.pi / sr),
+ ]).T * 0.1
+
+
+def generate_input_wav():
+ soundfile.write(wav_file, generate_samples(), samplerate=sr, subtype="PCM_24")
+
+
+def generate_test_bwf(bwf_file=bwf_file):
+ args = ['ear-utils', 'make_test_bwf', '-m', meta_file, '-i', wav_file, bwf_file]
+ assert subprocess.call(args) == 0
+
+
+def test_generate(tmpdir):
+ """Check that the output of the test file generator does not change
+
+ If it should, delete ear/test/data/test_bwf.wav and run this test file as a module:
+
+ python -m ear.test.test_integrate
+ """
+ bwf_file_gen = str(tmpdir / "test_bwf.wav")
+ generate_test_bwf(bwf_file_gen)
+
+ # could check the rendering of this file instead, but it's good to spot
+ # changes to the output format even if they don't affect the rendering, so
+ # that compatibility with other systems can be checked
+ assert open(bwf_file_gen, 'rb').read() == open(bwf_file, 'rb').read()
+
+
+def test_render(tmpdir):
+ rendered_file = str(tmpdir / "test_bwf_render.wav")
+ args = ['ear-render', '-d', '-s', '4+5+0', bwf_file, rendered_file]
+ assert subprocess.call(args) == 0
+
+ samples, sr = soundfile.read(rendered_file)
+
+ expected_in = generate_samples()
+
+ # gains for each of the blocks
+ remap_first = np.zeros((4, 10))
+ remap_first[0, 2] = 1
+ remap_first[1, 6] = 1
+
+ remap_second = np.zeros((4, 10))
+ remap_second[0, 1] = 1
+ remap_second[1, 6] = 1
+
+ # apply gains to all samples
+ remapped_first = np.dot(expected_in, remap_first)
+ remapped_second = np.dot(expected_in, remap_second)
+
+ # then select based on sample number
+ sampleno = np.arange(len(expected_in))[:, np.newaxis]
+ expected = remapped_first * (sampleno < sr // 8) + remapped_second * (sampleno >= sr // 8)
+
+ # add in channel based content
+ expected[:, 7] += expected_in[:, 2]
+ expected[:, 0] += expected_in[:, 2]
+ expected[:, 3] += expected_in[:, 3]
+
+ npt.assert_allclose(samples, expected, atol=1e-6)
+
+
+def test_render_adapt(tmpdir):
+ meta_file = os.path.join(files_dir, "test_adapt_meta.yaml")
+ layout_file = os.path.join(files_dir, "test_adapt_speakers.yaml")
+ bwf_file = str(tmpdir / "test_adapt_bwf.wav")
+
+ args = ['ear-utils', 'make_test_bwf', '-m', meta_file, '-i', wav_file, bwf_file]
+ assert subprocess.call(args) == 0
+
+ rendered_file = str(tmpdir / "test_adapt_bwf_render.wav")
+ args = ['ear-render', '--layout', layout_file, '-s', '0+5+0', bwf_file, rendered_file]
+ assert subprocess.call(args) == 0
+
+ samples, sr = soundfile.read(rendered_file)
+
+ expected_in = generate_samples()[:, 0]
+
+ expected = np.zeros((len(expected_in), 7))
+ expected[:, 6] = expected_in
+
+ npt.assert_allclose(samples, expected, atol=1e-6)
+
+
+@pytest.mark.parametrize("order", [1, 2])
+@pytest.mark.parametrize("chna_only", [False, True])
+def test_hoa(tmpdir, order, chna_only):
+ from ..core.geom import cart, azimuth
+ from ..core import hoa
+
+ spk_azimuths = np.array([30, -30, 0, 110, -110])
+ spk_positions = cart(spk_azimuths, 0, 1)
+
+ n, m = hoa.from_acn(range((order+1)**2))
+ gains_for_channels = hoa.sph_harm(n[np.newaxis], m[np.newaxis],
+ np.radians(spk_azimuths[:, np.newaxis]), 0,
+ hoa.norm_SN3D)
+
+ if order == 1:
+ # checked with ambix tools
+ npt.assert_allclose(gains_for_channels,
+ np.array([
+ [ 1. , 0.5 , 0. , 0.8660254 ], # noqa
+ [ 1. , -0.5 , 0. , 0.8660254 ], # noqa
+ [ 1. , 0. , 0. , 1. ], # noqa
+ [ 1. , 0.93969262, 0. , -0.34202014], # noqa
+ [ 1. , -0.93969262, 0. , -0.34202014], # noqa
+ ]))
+
+ channels = [0, 1, 2, 4, 5]
+
+ sr = 48000
+ n_samples = sr // 10
+ overall_gain = 0.5
+
+ input_samples = np.repeat(gains_for_channels * overall_gain, n_samples, axis=0)
+
+ input_fname = str(tmpdir / "hoa_input.wav")
+ soundfile.write(input_fname, input_samples, samplerate=sr, subtype="PCM_24")
+
+ bwf_fname = str(tmpdir / "hoa_bwf.wav")
+ opts = ["--chna-only"] if chna_only else []
+ assert subprocess.call(["ear-utils", "ambix_to_bwf"] + opts + [input_fname, bwf_fname]) == 0
+
+ rendered_fname = str(tmpdir / "hoa_rendered.wav")
+ assert subprocess.call(["ear-render", "-d", "-s", "0+5+0", bwf_fname, rendered_fname]) == 0
+
+ rendered_samples, rendered_sr = soundfile.read(rendered_fname)
+ assert rendered_sr == sr
+
+ sample_positions = np.arange(len(channels)) * n_samples + n_samples // 2
+ rendered_gains = rendered_samples[sample_positions][:, channels]
+
+ # target channel should generally have the highest amplitude
+ if order >= 2:
+ assert np.all(np.argmax(rendered_gains, axis=1) == np.arange(len(channels)))
+
+ # velocity vectors should be approximately correct
+ rendered_positions = np.dot(rendered_gains, spk_positions)
+ rendered_azimuths = azimuth(rendered_positions)
+
+ atol = {1: 16, 2: 5}[order]
+ npt.assert_allclose(spk_azimuths, rendered_azimuths, atol=atol)
+
+
+if __name__ == "__main__":
+ regenerate = False
+
+ if not os.path.exists(wav_file):
+ print("regenerating {}".format(wav_file)) # noqa
+ generate_input_wav()
+ regenerate = True
+
+ if regenerate or not os.path.exists(bwf_file):
+ print("regenerating {}".format(bwf_file)) # noqa
+ generate_test_bwf()
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..76d7f122
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,61 @@
+from setuptools import setup, find_packages
+
+setup(
+ name='ear',
+ description='EBU ADM Renderer',
+ version='1.0.0',
+
+ url='https://github.com/ebu/ebu_adm_renderer',
+
+ author='EBU',
+ author_email='ear-admin@list.ebu.ch',
+ license='BSD-3-Clause',
+
+ long_description=open('README.md').read(),
+ long_description_content_type='text/markdown',
+
+ install_requires=[
+ 'numpy~=1.14',
+ 'scipy~=1.0',
+ 'attrs~=17.4',
+ 'ruamel.yaml~=0.15',
+ 'lxml~=4.2',
+ 'enum34~=1.1',
+ 'six~=1.11',
+ 'multipledispatch~=0.5',
+ ],
+
+ extras_require={
+ 'test': [
+ 'pytest~=3.5',
+ 'pytest-datafiles~=1.0',
+ 'pytest-cov~=2.5',
+ 'soundfile~=0.10',
+ ],
+ 'dev': [
+ 'flake8~=3.5',
+ 'flake8-print~=3.1',
+ 'flake8-putty~=0.4',
+ 'flake8-string-format~=0.2',
+ ],
+ },
+
+ packages=find_packages(),
+
+ package_data={
+ "ear.test": ["data/*.yaml", "data/*.wav"],
+ "ear.core": ["data/*.yaml", "data/*.dat"],
+ "ear.core.test": ["data/psp_pvs/*.npz"],
+ "ear.core.objectbased.test": ["data/gain_calc_pvs/*"],
+ "ear.fileio.adm": ["data/*.xml"],
+ "ear.fileio.adm.test": ["test_adm_files/*.xml"],
+ "ear.fileio.bw64.test": ["test_wav_files/*.wav"],
+ },
+
+ entry_points={
+ 'console_scripts': [
+ 'ear-render = ear.cmdline.render_file:main',
+ 'ear-utils = ear.cmdline.utils:main'
+ ]
+ },
+)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000..5f1784d1
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,32 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py27, py36
+
+[testenv]
+usedevelop = false
+changedir = {envdir}
+commands = py.test -c {toxinidir}/tox.ini --cov=ear {posargs}
+extras = test
+
+[pytest]
+python_files = *.py
+testpaths = ear
+addopts = --doctest-modules --pyargs
+
+[coverage:run]
+omit = */test/*
+
+[flake8]
+ignore = E701,E702,E226,P101
+max-line-length = 160
+exclude = env*,.git,.tox
+doctests = true
+putty-ignore =
+ /@dispatch\(/ : F811
+ /# noqa: *(?P[A-Z0-9, ]*)/ : +(?P)
+ /# noqa$/ : +E201,E203,E241,E202
+ /".format/ : +P103