changeset 244:ee6d2294cdd5

Add metadata support to AudioRegion
author Amine Sehili <amine.sehili@gmail.com>
date Wed, 31 Jul 2019 20:07:27 +0100
parents f55007434c6b
children 1dfba457a9e1
files auditok/core.py tests/test_core.py
diffstat 2 files changed, 239 insertions(+), 271 deletions(-) [+]
line wrap: on
line diff
--- a/auditok/core.py	Mon Jul 29 20:37:31 2019 +0100
+++ b/auditok/core.py	Wed Jul 31 20:07:27 2019 +0100
@@ -267,7 +267,9 @@
     """
     start = start_frame * frame_duration
     data = b"".join(data_frames)
-    return AudioRegion(data, start, sampling_rate, sample_width, channels)
+    duration = len(data) / (sampling_rate * sample_width * channels)
+    meta = {"start": start, "end": start + duration}
+    return AudioRegion(data, sampling_rate, sample_width, channels, meta)
 
 
 def _check_convert_index(index, types, err_msg):
@@ -308,8 +310,26 @@
         return super(_MillisView, self).__getitem__(index)
 
 
+class _AudioRegionMetadata(dict):
+    def __getattr__(self, name):
+        if name in self:
+            return self[name]
+        else:
+            err_msg = "AudioRegion metadata has no entry '{}'"
+            raise AttributeError(err_msg.format(name))
+
+    def __setattr__(self, name, value):
+        self[name] = value
+
+    def __str__(self):
+        return "\n".join("{}: {}".format(k, v) for k, v in self.items())
+
+    def __repr__(self):
+        return str(self)
+
+
 class AudioRegion(object):
-    def __init__(self, data, start, sampling_rate, sample_width, channels):
+    def __init__(self, data, sampling_rate, sample_width, channels, meta=None):
         """
         A class for detected audio events.
 
@@ -317,8 +337,6 @@
 
             data: bytes
                 audio data
-            start: float
-                start time in seconds
             samling_rate: int
                 sampling rate of audio data
             sample_width: int
@@ -328,17 +346,29 @@
         """
         check_audio_data(data, sample_width, channels)
         self._data = data
-        self._start = start
         self._sampling_rate = sampling_rate
         self._sample_width = sample_width
         self._channels = channels
 
+        if meta is not None:
+            self._meta = _AudioRegionMetadata(meta)
+        else:
+            self._meta = None
+
         self._seconds_view = _SecondsView(self)
         self.s = self.sec
 
         self._millis_view = _MillisView(self)
         self.ms = self.millis
 
+    @property
+    def meta(self):
+        return self._meta
+
+    @meta.setter
+    def meta(self, new_meta):
+        self._meta = _AudioRegionMetadata(new_meta)
+
     @classmethod
     def load(cls, file, skip=0, max_read=None, **kwargs):
         audio_source = get_audio_source(file, **kwargs)
@@ -354,7 +384,6 @@
         audio_source.close()
         return cls(
             data,
-            0,
             audio_source.sampling_rate,
             audio_source.sample_width,
             audio_source.channels,
@@ -369,14 +398,6 @@
         return self._millis_view
 
     @property
-    def start(self):
-        return self._start
-
-    @property
-    def end(self):
-        return self.start + self.duration
-
-    @property
     def duration(self):
         """
         Returns region duration in seconds.
@@ -438,24 +459,9 @@
 
         file: str, file-like object
             path to output file or a file-like object. If ´str´, it may contain
-            ´{start}´, ´{end}´ and ´{duration}´ place holders, they'll be
-            replaced by region's ´start´, ´end´ and ´duration´ respectively.
-            Example:
+            and ´{duration}´ place holders as well as any place holder that
+            this region's metadata might contain (e.g., ´{meta.start}´).
 
-            .. code:: python
-                region = AudioRegion(b'\0' * 2 * 24000,
-                                     start=2.25,
-                                     sampling_rate=16000,
-                                     sample_width=2,
-                                     channels=1)
-                region.duration
-                1.5
-                region.end
-                3.75
-                region.save('audio_{start}-{end}.wav')
-                audio_2.25-3.75.wav
-                region.save('audio_{duration:.3f}_{start:.3f}-{end:.3f}.wav')
-                audio_1.500_2.250-3.750.wav
 
         format: str
             type of audio file. If None (default), file type is guessed from
@@ -478,13 +484,22 @@
         :Raises:
 
         IOError if ´file´ exists and ´exists_ok´ is False.
+
+        Example:
+
+        .. code:: python
+            region = AudioRegion(b'\0' * 2 * 24000,
+                                    sampling_rate=16000,
+                                    sample_width=2,
+                                    channels=1)
+            region.meta = {"start": 2.25, "end": 2.25 + region.duration}
+            region.save('audio_{meta.start}-{meta.end}.wav')
+            audio_2.25-3.75.wav
+            region.save('region_{meta.start:.3f}_{duration:.3f}.wav')
+            audio_2.250_1.500.wav
         """
         if isinstance(file, str):
-            file = file.format(
-                start=round(self.start, 6),
-                end=round(self.end, 6),
-                duration=round(self.duration, 6),
-            )
+            file = file.format(duration=self.duration, meta=self.meta)
             if not exists_ok and os.path.exists(file):
                 raise FileExistsError("file '{file}' exists".format(file=file))
         to_file(
@@ -507,18 +522,16 @@
     def __bytes__(self):
         return self._data
 
-    def __repr__(self):
+    def __str__(self):
         return (
-            "AudioRegion(data, start={:.3f}, end={:.3f}, "
+            "AudioRegion(duration={:.3f}, "
             "sampling_rate={}, sample_width={}, channels={})".format(
-                self.start, self.end, self.sr, self.sw, self.ch
+                self.duration, self.sr, self.sw, self.ch
             )
         )
 
-    def __str__(self):
-        return "AudioRegion(start={:.3f}, end={:.3f}, duration={:.3f}".format(
-            self.start, self.end, self.duration
-        )
+    def __repr__(self):
+        return str(self)
 
     def __add__(self, other):
         """
@@ -547,7 +560,7 @@
                 "number of channels ({} != {})".format(self.ch, other.ch)
             )
         data = self._data + other._data
-        return AudioRegion(data, self.start, self.sr, self.sw, self.ch)
+        return AudioRegion(data, self.sr, self.sw, self.ch)
 
     def __radd__(self, other):
         """
@@ -565,7 +578,7 @@
             err_msg = "Can't multiply AudioRegion by a non-int of type '{}'"
             raise TypeError(err_msg.format(type(n)))
         data = self._data * n
-        return AudioRegion(data, self.start, self.sr, self.sw, self.ch)
+        return AudioRegion(data, self.sr, self.sw, self.ch)
 
     def __rmul__(self, n):
         return self * n
@@ -602,8 +615,7 @@
             offset = None
 
         data = self._data[onset:offset]
-        new_start = self.start + start_sample / self.sampling_rate
-        return AudioRegion(data, new_start, self.sr, self.sw, self.ch)
+        return AudioRegion(data, self.sr, self.sw, self.ch)
 
 
 class StreamTokenizer:
--- a/tests/test_core.py	Mon Jul 29 20:37:31 2019 +0100
+++ b/tests/test_core.py	Wed Jul 31 20:07:27 2019 +0100
@@ -21,10 +21,7 @@
     for b in byte_seq:
         duration = round(random() * 10, 6)
         data = b * int(duration * sampling_rate) * sample_width * channels
-        start = round(random() * 13, 3)
-        region = AudioRegion(
-            data, start, sampling_rate, sample_width, channels
-        )
+        region = AudioRegion(data, sampling_rate, sample_width, channels)
         regions.append(region)
     return regions
 
@@ -611,7 +608,6 @@
         audio_region=(
             AudioRegion(
                 open("tests/data/test_split_10HZ_stereo.raw", "rb").read(),
-                0,
                 10,
                 2,
                 2,
@@ -916,17 +912,16 @@
         expected_duration_s,
         expected_duration_ms,
     ):
-        region = AudioRegion(
-            data, start, sampling_rate, sample_width, channels
-        )
+        meta = {"start": start, "end": expected_end}
+        region = AudioRegion(data, sampling_rate, sample_width, channels, meta)
         self.assertEqual(region.sampling_rate, sampling_rate)
         self.assertEqual(region.sr, sampling_rate)
         self.assertEqual(region.sample_width, sample_width)
         self.assertEqual(region.sw, sample_width)
         self.assertEqual(region.channels, channels)
         self.assertEqual(region.ch, channels)
-        self.assertEqual(region.start, start)
-        self.assertEqual(region.end, expected_end)
+        self.assertEqual(region.meta.start, start)
+        self.assertEqual(region.meta.end, expected_end)
         self.assertEqual(region.duration, expected_duration_s)
         self.assertEqual(len(region), expected_duration_ms)
         self.assertEqual(bytes(region), data)
@@ -934,11 +929,7 @@
     def test_creation_invalid_data_exception(self):
         with self.assertRaises(AudioParameterError) as audio_param_err:
             _ = AudioRegion(
-                data=b"ABCDEFGHI",
-                start=0,
-                sampling_rate=8,
-                sample_width=2,
-                channels=1,
+                data=b"ABCDEFGHI", sampling_rate=8, sample_width=2, channels=1
             )
         self.assertEqual(
             "The length of audio data must be an integer "
@@ -948,29 +939,31 @@
 
     @genty_dataset(
         simple=("output.wav", 1.230, "output.wav"),
-        start=("output_{start}.wav", 1.230, "output_1.23.wav"),
-        start_2=("output_{start}.wav", 1.233712, "output_1.233712.wav"),
-        start_3=("output_{start}.wav", 1.2300001, "output_1.23.wav"),
-        start_4=("output_{start:.3f}.wav", 1.233712, "output_1.234.wav"),
+        start=("output_{meta.start:g}.wav", 1.230, "output_1.23.wav"),
+        start_2=("output_{meta.start}.wav", 1.233712, "output_1.233712.wav"),
+        start_3=("output_{meta.start:.2f}.wav", 1.2300001, "output_1.23.wav"),
+        start_4=("output_{meta.start:.3f}.wav", 1.233712, "output_1.234.wav"),
         start_5=(
-            "output_{start:.8f}.wav",
-            1.233712345,
+            "output_{meta.start:.8f}.wav",
+            1.233712,
             "output_1.23371200.wav",
         ),
         start_end_duration=(
-            "output_{start}_{end}_{duration}.wav",
+            "output_{meta.start}_{meta.end}_{duration}.wav",
             1.455,
             "output_1.455_2.455_1.0.wav",
         ),
         start_end_duration_2=(
-            "output_{start}_{end}_{duration}.wav",
+            "output_{meta.start}_{meta.end}_{duration}.wav",
             1.455321,
             "output_1.455321_2.455321_1.0.wav",
         ),
     )
     def test_save(self, format, start, expected):
         with TemporaryDirectory() as tmpdir:
-            region = AudioRegion(b"0" * 160, start, 160, 1, 1)
+            region = AudioRegion(b"0" * 160, 160, 1, 1)
+            meta = {"start": start, "end": start + region.duration}
+            region.meta = meta
             format = os.path.join(tmpdir, format)
             filename = region.save(format)[len(tmpdir) + 1 :]
             self.assertEqual(filename, expected)
@@ -979,279 +972,252 @@
         with TemporaryDirectory() as tmpdir:
             filename = os.path.join(tmpdir, "output.wav")
             open(filename, "w").close()
-            region = AudioRegion(b"0" * 160, 0, 160, 1, 1)
+            region = AudioRegion(b"0" * 160, 160, 1, 1)
             with self.assertRaises(FileExistsError):
                 region.save(filename, exists_ok=False)
 
     @genty_dataset(
         first_half=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
             slice(0, 500),
+            b"a" * 80,
+        ),
+        second_half=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(500, None),
+            b"b" * 80,
+        ),
+        second_half_negative=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(-500, None),
+            b"b" * 80,
+        ),
+        middle=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(200, 750),
+            b"a" * 48 + b"b" * 40,
+        ),
+        middle_negative=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(-800, -250),
+            b"a" * 48 + b"b" * 40,
+        ),
+        middle_sw2=(
+            AudioRegion(b"a" * 160 + b"b" * 160, 160, 2, 1),
+            slice(200, 750),
+            b"a" * 96 + b"b" * 80,
+        ),
+        middle_ch2=(
+            AudioRegion(b"a" * 160 + b"b" * 160, 160, 1, 2),
+            slice(200, 750),
+            b"a" * 96 + b"b" * 80,
+        ),
+        middle_sw2_ch2=(
+            AudioRegion(b"a" * 320 + b"b" * 320, 160, 2, 2),
+            slice(200, 750),
+            b"a" * 192 + b"b" * 160,
+        ),
+        but_first_sample=(
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
+            slice(1, None),
+            b"a" * (4000 - 8) + b"b" * 4000,
+        ),
+        but_first_sample_negative=(
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
+            slice(-999, None),
+            b"a" * (4000 - 8) + b"b" * 4000,
+        ),
+        but_last_sample=(
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
+            slice(0, 999),
+            b"a" * 4000 + b"b" * (4000 - 8),
+        ),
+        but_last_sample_negative=(
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
+            slice(0, -1),
+            b"a" * 4000 + b"b" * (4000 - 8),
+        ),
+        big_negative_start=(
+            AudioRegion(b"a" * 160, 160, 1, 1),
+            slice(-5000, None),
+            b"a" * 160,
+        ),
+        big_negative_stop=(
+            AudioRegion(b"a" * 160, 160, 1, 1),
+            slice(None, -1500),
+            b"",
+        ),
+        empty=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(0, 0),
+            b"",
+        ),
+        empty_start_stop_reversed=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(200, 100),
+            b"",
+        ),
+        empty_big_positive_start=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(2000, 3000),
+            b"",
+        ),
+        empty_negative_reversed=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(-100, -200),
+            b"",
+        ),
+        empty_big_negative_stop=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(0, -2000),
+            b"",
+        ),
+        arbitrary_sampling_rate=(
+            AudioRegion(b"a" * 124 + b"b" * 376, 1234, 1, 1),
+            slice(100, 200),
+            b"a" + b"b" * 123,
+        ),
+    )
+    def test_region_temporal_slicing(self, region, slice_, expected_data):
+        sub_region = region.millis[slice_]
+        self.assertEqual(bytes(sub_region), expected_data)
+        start_sec = slice_.start / 1000 if slice_.start is not None else None
+        stop_sec = slice_.stop / 1000 if slice_.stop is not None else None
+        sub_region = region.sec[start_sec:stop_sec]
+        self.assertEqual(bytes(sub_region), expected_data)
+
+    @genty_dataset(
+        first_half=(
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(0, 80),
             0,
             b"a" * 80,
         ),
         second_half=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(500, None),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(80, None),
             0.5,
             b"b" * 80,
         ),
         second_half_negative=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(-500, None),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(-80, None),
             0.5,
             b"b" * 80,
         ),
         middle=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(200, 750),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(160 // 5, 160 // 4 * 3),
             0.2,
             b"a" * 48 + b"b" * 40,
         ),
         middle_negative=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(-800, -250),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(-160 // 5 * 4, -160 // 4),
             0.2,
             b"a" * 48 + b"b" * 40,
         ),
         middle_sw2=(
-            AudioRegion(b"a" * 160 + b"b" * 160, 0, 160, 2, 1),
-            slice(200, 750),
+            AudioRegion(b"a" * 160 + b"b" * 160, 160, 2, 1),
+            slice(160 // 5, 160 // 4 * 3),
             0.2,
             b"a" * 96 + b"b" * 80,
         ),
         middle_ch2=(
-            AudioRegion(b"a" * 160 + b"b" * 160, 0, 160, 1, 2),
-            slice(200, 750),
+            AudioRegion(b"a" * 160 + b"b" * 160, 160, 1, 2),
+            slice(160 // 5, 160 // 4 * 3),
             0.2,
             b"a" * 96 + b"b" * 80,
         ),
         middle_sw2_ch2=(
-            AudioRegion(b"a" * 320 + b"b" * 320, 0, 160, 2, 2),
-            slice(200, 750),
+            AudioRegion(b"a" * 320 + b"b" * 320, 160, 2, 2),
+            slice(160 // 5, 160 // 4 * 3),
             0.2,
             b"a" * 192 + b"b" * 160,
         ),
         but_first_sample=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
             slice(1, None),
-            0.001,
-            b"a" * (4000 - 8) + b"b" * 4000,
+            1 / 8000,
+            b"a" * (4000 - 1) + b"b" * 4000,
         ),
         but_first_sample_negative=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
-            slice(-999, None),
-            0.001,
-            b"a" * (4000 - 8) + b"b" * 4000,
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
+            slice(-7999, None),
+            1 / 8000,
+            b"a" * (4000 - 1) + b"b" * 4000,
         ),
         but_last_sample=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
-            slice(0, 999),
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
+            slice(0, 7999),
             0,
-            b"a" * 4000 + b"b" * (4000 - 8),
+            b"a" * 4000 + b"b" * (4000 - 1),
         ),
         but_last_sample_negative=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
+            AudioRegion(b"a" * 4000 + b"b" * 4000, 8000, 1, 1),
             slice(0, -1),
             0,
-            b"a" * 4000 + b"b" * (4000 - 8),
+            b"a" * 4000 + b"b" * (4000 - 1),
         ),
         big_negative_start=(
-            AudioRegion(b"a" * 160, 0, 160, 1, 1),
-            slice(-5000, None),
+            AudioRegion(b"a" * 160, 160, 1, 1),
+            slice(-1600, None),
             0,
             b"a" * 160,
         ),
         big_negative_stop=(
-            AudioRegion(b"a" * 160, 0, 160, 1, 1),
-            slice(None, -1500),
+            AudioRegion(b"a" * 160, 160, 1, 1),
+            slice(None, -1600),
             0,
             b"",
         ),
         empty=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
             slice(0, 0),
             0,
             b"",
         ),
         empty_start_stop_reversed=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(200, 100),
-            0.2,
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(80, 40),
+            0.5,
             b"",
         ),
         empty_big_positive_start=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(2000, 3000),
-            2,
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(1600, 3000),
+            10,
             b"",
         ),
         empty_negative_reversed=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(-100, -200),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
+            slice(-16, -32),
             0.9,
             b"",
         ),
         empty_big_negative_stop=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
+            AudioRegion(b"a" * 80 + b"b" * 80, 160, 1, 1),
             slice(0, -2000),
             0,
             b"",
         ),
         arbitrary_sampling_rate=(
-            AudioRegion(b"a" * 124 + b"b" * 376, 0, 1234, 1, 1),
-            slice(100, 200),
-            123 / 1234,
-            b"a" + b"b" * 123,
-        ),
-    )
-    def test_region_temporal_slicing(
-        self, region, slice_, expected_start, expected_data
-    ):
-        sub_region = region.millis[slice_]
-        self.assertEqual(sub_region.start, expected_start)
-        self.assertEqual(bytes(sub_region), expected_data)
-
-        start_sec = slice_.start / 1000 if slice_.start is not None else None
-        stop_sec = slice_.stop / 1000 if slice_.stop is not None else None
-
-        sub_region = region.sec[start_sec:stop_sec]
-        self.assertEqual(sub_region.start, expected_start)
-        self.assertEqual(bytes(sub_region), expected_data)
-
-    @genty_dataset(
-        first_half=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(0, 80),
-            0,
-            b"a" * 80,
-        ),
-        second_half=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(80, None),
-            0.5,
-            b"b" * 80,
-        ),
-        second_half_negative=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(-80, None),
-            0.5,
-            b"b" * 80,
-        ),
-        middle=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(160 // 5, 160 // 4 * 3),
-            0.2,
-            b"a" * 48 + b"b" * 40,
-        ),
-        middle_negative=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(-160 // 5 * 4, -160 // 4),
-            0.2,
-            b"a" * 48 + b"b" * 40,
-        ),
-        middle_sw2=(
-            AudioRegion(b"a" * 160 + b"b" * 160, 0, 160, 2, 1),
-            slice(160 // 5, 160 // 4 * 3),
-            0.2,
-            b"a" * 96 + b"b" * 80,
-        ),
-        middle_ch2=(
-            AudioRegion(b"a" * 160 + b"b" * 160, 0, 160, 1, 2),
-            slice(160 // 5, 160 // 4 * 3),
-            0.2,
-            b"a" * 96 + b"b" * 80,
-        ),
-        middle_sw2_ch2=(
-            AudioRegion(b"a" * 320 + b"b" * 320, 0, 160, 2, 2),
-            slice(160 // 5, 160 // 4 * 3),
-            0.2,
-            b"a" * 192 + b"b" * 160,
-        ),
-        but_first_sample=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
-            slice(1, None),
-            1 / 8000,
-            b"a" * (4000 - 1) + b"b" * 4000,
-        ),
-        but_first_sample_negative=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
-            slice(-7999, None),
-            1 / 8000,
-            b"a" * (4000 - 1) + b"b" * 4000,
-        ),
-        but_last_sample=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
-            slice(0, 7999),
-            0,
-            b"a" * 4000 + b"b" * (4000 - 1),
-        ),
-        but_last_sample_negative=(
-            AudioRegion(b"a" * 4000 + b"b" * 4000, 0, 8000, 1, 1),
-            slice(0, -1),
-            0,
-            b"a" * 4000 + b"b" * (4000 - 1),
-        ),
-        big_negative_start=(
-            AudioRegion(b"a" * 160, 0, 160, 1, 1),
-            slice(-1600, None),
-            0,
-            b"a" * 160,
-        ),
-        big_negative_stop=(
-            AudioRegion(b"a" * 160, 0, 160, 1, 1),
-            slice(None, -1600),
-            0,
-            b"",
-        ),
-        empty=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(0, 0),
-            0,
-            b"",
-        ),
-        empty_start_stop_reversed=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(80, 40),
-            0.5,
-            b"",
-        ),
-        empty_big_positive_start=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(1600, 3000),
-            10,
-            b"",
-        ),
-        empty_negative_reversed=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(-16, -32),
-            0.9,
-            b"",
-        ),
-        empty_big_negative_stop=(
-            AudioRegion(b"a" * 80 + b"b" * 80, 0, 160, 1, 1),
-            slice(0, -2000),
-            0,
-            b"",
-        ),
-        arbitrary_sampling_rate=(
-            AudioRegion(b"a" * 124 + b"b" * 376, 0, 1235, 1, 1),
+            AudioRegion(b"a" * 124 + b"b" * 376, 1235, 1, 1),
             slice(100, 200),
             100 / 1235,
             b"a" * 24 + b"b" * 76,
         ),
         arbitrary_sampling_rate_middle_sw2_ch2=(
-            AudioRegion(b"a" * 124 + b"b" * 376, 0, 1235, 2, 2),
+            AudioRegion(b"a" * 124 + b"b" * 376, 1235, 2, 2),
             slice(25, 50),
             25 / 1235,
             b"a" * 24 + b"b" * 76,
         ),
     )
     def test_region_sample_slicing(
-        self, region, slice_, expected_start, expected_data
+        self, region, slice_, time_shift, expected_data
     ):
         sub_region = region[slice_]
-        self.assertEqual(sub_region.start, expected_start)
         self.assertEqual(bytes(sub_region), expected_data)
 
     @genty_dataset(
@@ -1264,15 +1230,9 @@
         region_1, region_2 = _make_random_length_regions(
             [b"a", b"b"], sampling_rate, sample_width, channels
         )
-
-        expected_start = region_1.start
         expected_duration = region_1.duration + region_2.duration
-        expected_end = expected_start + expected_duration
         expected_data = bytes(region_1) + bytes(region_2)
         concat_region = region_1 + region_2
-
-        self.assertEqual(concat_region.start, expected_start)
-        self.assertAlmostEqual(concat_region.end, expected_end, places=6)
         self.assertAlmostEqual(
             concat_region.duration, expected_duration, places=6
         )
@@ -1288,14 +1248,10 @@
         regions = _make_random_length_regions(
             [b"a", b"b", b"c"], sampling_rate, sample_width, channels
         )
-        expected_start = regions[0].start
         expected_duration = sum(r.duration for r in regions)
-        expected_end = expected_start + expected_duration
         expected_data = b"".join(bytes(r) for r in regions)
         concat_region = sum(regions)
 
-        self.assertEqual(concat_region.start, expected_start)
-        self.assertAlmostEqual(concat_region.end, expected_end, places=6)
         self.assertAlmostEqual(
             concat_region.duration, expected_duration, places=6
         )
@@ -1303,8 +1259,8 @@
 
     def test_concatenation_different_sampling_rate_error(self):
 
-        region_1 = AudioRegion(b"a" * 100, 0, 8000, 1, 1)
-        region_2 = AudioRegion(b"b" * 100, 0, 3000, 1, 1)
+        region_1 = AudioRegion(b"a" * 100, 8000, 1, 1)
+        region_2 = AudioRegion(b"b" * 100, 3000, 1, 1)
 
         with self.assertRaises(ValueError) as val_err:
             region_1 + region_2
@@ -1316,8 +1272,8 @@
 
     def test_concatenation_different_sample_width_error(self):
 
-        region_1 = AudioRegion(b"a" * 100, 0, 8000, 2, 1)
-        region_2 = AudioRegion(b"b" * 100, 0, 8000, 4, 1)
+        region_1 = AudioRegion(b"a" * 100, 8000, 2, 1)
+        region_2 = AudioRegion(b"b" * 100, 8000, 4, 1)
 
         with self.assertRaises(ValueError) as val_err:
             region_1 + region_2
@@ -1329,8 +1285,8 @@
 
     def test_concatenation_different_number_of_channels_error(self):
 
-        region_1 = AudioRegion(b"a" * 100, 0, 8000, 1, 1)
-        region_2 = AudioRegion(b"b" * 100, 0, 8000, 1, 2)
+        region_1 = AudioRegion(b"a" * 100, 8000, 1, 1)
+        region_2 = AudioRegion(b"b" * 100, 8000, 1, 2)
 
         with self.assertRaises(ValueError) as val_err:
             region_1 + region_2
@@ -1350,7 +1306,7 @@
     ):
         sw = 2
         data = b"0" * int(duration * 8000 * sw)
-        region = AudioRegion(data, 0, 8000, sw, 1)
+        region = AudioRegion(data, 8000, sw, 1)
         m_region = 1 * region * 3
         self.assertEqual(bytes(m_region), data * 3)
         self.assertEqual(m_region.sr, 8000)
@@ -1362,6 +1318,6 @@
     @genty_dataset(_str=("x", "str"), _float=(1.4, "float"))
     def test_multiplication_non_int(self, factor, _type):
         with self.assertRaises(TypeError) as type_err:
-            AudioRegion(b"0" * 80, 0, 8000, 1, 1) * factor
+            AudioRegion(b"0" * 80, 8000, 1, 1) * factor
             err_msg = "Can't multiply AudioRegion by a non-int of type '{}'"
             self.assertEqual(err_msg.format(_type), str(type_err.exception))