Repository: pyfar/pyfar
Issue: read/convert sofa data of type FIR-E

## General

* pyfar version: 0.5.1

## Description

Sofa data of type FIR-E contains the emitters (not the time samples) in the last data dimension. We need to change axis before loading the data.

Changed files:
tests/conftest.py, pyfar/io/io.py, tests/test_io.py

diff --git a/pyfar/io/io.py b/pyfar/io/io.py
index 4169c037..b9a2b680 100644
--- a/pyfar/io/io.py
+++ b/pyfar/io/io.py
@@ -105,10 +105,16 @@ def convert_sofa(sofa):
 
         - :py:class:`~pyfar.classes.audio.Signal`
             A Signal object is returned is the DataType is ``'FIR'``,
-            ``'FIR-E'``, or ``'FIRE'``.
+            ``'FIR-E'``, or ``'FIRE'``. In case of ``'FIR-E'``, the time data
+            is returned with the `cshape` EMRN (samples are in the last
+            dimension) and not MRNE as in the SOFA standard (emitters are in
+            the last dimension).
         - :py:class:`~pyfar.classes.audio.FrequencyData`
             A FrequencyData object is returned is the DataType is ``'TF'``,
-            ``'TF-E'``, or ``'TFE'``.
+            ``'TF-E'``, or ``'TFE'``. In case of ``'TF-E'``, the frequency data
+            is returned with the `cshape` EMRN (frequencies are in the last
+            dimension) and not MRNE as in the SOFA standard (emitters are in
+            the last dimension).
 
         The `cshape` of the object is is ``(M, R)`` with `M` being the number
         of measurements and `R` being the number of receivers from the SOFA
@@ -135,16 +141,30 @@ def convert_sofa(sofa):
 
     # Check for DataType
     if sofa.GLOBAL_DataType in ['FIR', 'FIR-E', 'FIRE']:
+        # order axis according to pyfar convention
+        # (samples go in last dimension)
+        if sofa.GLOBAL_DataType == 'FIR-E':
+            time = np.moveaxis(sofa.Data_IR, -1, 0)
+        else:
+            time = sofa.Data_IR
+
         # make a Signal
-        signal = Signal(sofa.Data_IR, sofa.Data_SamplingRate)
+        signal = Signal(time, sofa.Data_SamplingRate)
 
     elif sofa.GLOBAL_DataType in ['TF', 'TF-E', 'TFE']:
+        # order axis according to pyfar convention
+        # frequencies go in last dimension)
+        if sofa.GLOBAL_DataType == 'TF-E':
+            freq = np.moveaxis(sofa.Data_Real, -1, 0) + \\
+                1j * np.moveaxis(sofa.Data_Imag, -1, 0)
+        else:
+            freq = sofa.Data_Real + 1j * sofa.Data_Imag
+
         # make FrequencyData
-        signal = FrequencyData(
-            sofa.Data_Real + 1j * sofa.Data_Imag, sofa.N)
+        signal = FrequencyData(freq, sofa.N)
     else:
         raise ValueError(
-            "DataType {sofa.GLOBAL_DataType} is not supported.")
+            f"DataType {sofa.GLOBAL_DataType} is not supported.")
 
     # Source
     s_values = sofa.SourcePosition
diff --git a/tests/conftest.py b/tests/conftest.py
index 6b1a94ad..db7718a5 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -502,6 +502,35 @@ def generate_sofa_GeneralTF(
     return filename
 
 
+@pytest.fixture
+def generate_sofa_GeneralFIR_E(tmpdir):
+    """ Generate the reference sofa files of type GeneralFIR-E."""
+    filename = os.path.join(tmpdir, ('GeneralFIR-E.sofa'))
+
+    sofafile = sf.Sofa('GeneralFIR-E', True)
+    sofafile.Data_IR = np.zeros((4, 2, 10, 3))
+    sofafile.Data_Delay = np.zeros((4, 2, 3))
+
+    sf.write_sofa(filename, sofafile)
+
+    return filename
+
+
+@pytest.fixture
+def generate_sofa_GeneralTF_E(tmpdir):
+    """ Generate the reference sofa files of type GeneralFIR-E."""
+    filename = os.path.join(tmpdir, ('GeneralTF-E.sofa'))
+
+    sofafile = sf.Sofa('GeneralTF-E', True)
+    sofafile.Data_Real = np.ones((4, 2, 10, 3))
+    sofafile.Data_Imag = np.zeros((4, 2, 10, 3))
+    sofafile.N = np.arange(10)
+
+    sf.write_sofa(filename, sofafile)
+
+    return filename
+
+
 @pytest.fixture
 def generate_sofa_postype_spherical(
         tmpdir, noise_two_by_three_channel, sofa_reference_coordinates):
diff --git a/tests/test_io.py b/tests/test_io.py
index 5bb05822..2df7dc7a 100644
--- a/tests/test_io.py
+++ b/tests/test_io.py
@@ -32,6 +32,18 @@ def test_read_sofa_GeneralTF(
     npt.assert_allclose(signal.freq, noise_two_by_three_channel.freq)
 
 
+def test_read_sofa_GeneralFIR_E(generate_sofa_GeneralFIR_E):
+    "Test order of axis for emitter dependent FIR data"
+    signal = io.read_sofa(generate_sofa_GeneralFIR_E)[0]
+    assert signal.cshape == (3, 4, 2)
+
+
+def test_read_sofa_GeneralTF_E(generate_sofa_GeneralTF_E):
+    "Test order of axis for emitter dependent TF data"
+    signal = io.read_sofa(generate_sofa_GeneralTF_E)[0]
+    assert signal.cshape == (3, 4, 2)
+
+
 def test_read_sofa_coordinates(
         generate_sofa_GeneralFIR, sofa_reference_coordinates):
     """Test for reading coordinates in sofa file"""