changeset 228:a51015e6f90d

Add temporal slicing views for AudioRegion
author Amine Sehili <amine.sehili@gmail.com>
date Sat, 13 Jul 2019 11:00:41 +0100
parents bbec5207a3b4
children 19b8f4f28d7b
files auditok/core.py
diffstat 1 files changed, 70 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/auditok/core.py	Fri Jul 12 20:14:26 2019 +0100
+++ b/auditok/core.py	Sat Jul 13 11:00:41 2019 +0100
@@ -245,6 +245,44 @@
     return AudioRegion(data, start, sampling_rate, sample_width, channels)
 
 
+def _check_convert_index(index, types, err_msg):
+    if not isinstance(index, slice) or index.step is not None:
+        raise TypeError(err_msg)
+    start = index.start if index.start is not None else 0
+    stop = index.stop
+    for index in (start, stop):
+        if index is not None and not isinstance(index, types):
+            raise TypeError(err_msg)
+    return start, stop
+
+
+class _SecondsView:
+    def __init__(self, region):
+        self._region = region
+
+    def __getitem__(self, index):
+        err_msg = "Slicing AudioRegion by seconds requires indices of type "
+        err_msg += "'int' or 'float' without a step (e.g. region.sec[7.5:10])"
+        start_s, stop_s = _check_convert_index(index, (int, float), err_msg)
+        sr = self._region.sampling_rate
+        start_sample = int(start_s * sr)
+        stop_sample = None if stop_s is None else round(stop_s * sr)
+        return self._region[start_sample:stop_sample]
+
+
+class _MillisView(_SecondsView):
+    def __getitem__(self, index):
+        err_msg = (
+            "Slicing AudioRegion by milliseconds requires indices of type "
+        )
+        err_msg += "'int' without a step (e.g. region.sec[500:1500])"
+        start_ms, stop_ms = _check_convert_index(index, (int), err_msg)
+        start_sec = start_ms / 1000
+        stop_sec = None if stop_ms is None else stop_ms / 1000
+        index = slice(start_sec, stop_sec)
+        return super(_MillisView, self).__getitem__(index)
+
+
 class AudioRegion(object):
     def __init__(self, data, start, sampling_rate, sample_width, channels):
         """
@@ -270,6 +308,20 @@
         self._sample_width = sample_width
         self._channels = channels
 
+        self._seconds_view = _SecondsView(self)
+        self.s = self.sec
+
+        self._millis_view = _MillisView(self)
+        self.ms = self.millis
+
+    @property
+    def sec(self):
+        return self._seconds_view
+
+    @property
+    def millis(self):
+        return self._millis_view
+
     @property
     def start(self):
         return self._start
@@ -402,7 +454,7 @@
 
     def __len__(self):
         """
-        Rerurns region duration in milliseconds.
+        Returns region duration in milliseconds.
         """
         return round(self.duration * 1000)
 
@@ -491,25 +543,26 @@
         if index.step is not None:
             raise ValueError(err_msg)
 
-        start_ms = index.start if index.start is not None else 0
-        stop_ms = index.stop if index.stop is not None else len(self)
-        if not (isinstance(start_ms, int) and isinstance(stop_ms, int)):
-            raise TypeError("Slicing Audioregion requires integers")
+        bytes_per_sample = self.sample_width * self.channels
+        len_samples = len(self._data) // bytes_per_sample
 
-        if start_ms < 0:
-            start_ms = max(start_ms + len(self), 0)
-        if stop_ms < 0:
-            stop_ms = max(stop_ms + len(self), 0)
+        if index.start is None:
+            start_sample = None
+        else:
+            start_sample = index.start
+            if start_sample < 0:
+                start_sample = max(start_sample + len_samples, 0)
+        onset = start_sample * bytes_per_sample
 
-        samples_per_ms = self.sr / 1000
-        bytes_per_ms = samples_per_ms * self.sw * self.channels
-        # if a fraction of a sample is covered, return the whole sample
-        onset = int(start_ms * bytes_per_ms)
-        offset = round(stop_ms * bytes_per_ms + 0.5)
-        # recompute start_ms based on actual onset
-        actual_start_s = onset / bytes_per_ms / 1000
-        new_start = self.start + actual_start_s
+        if index.stop is None:
+            offset = None
+        elif index.stop < 0:
+            offset = max(index.stop + len_samples, 0) * bytes_per_sample
+        else:
+            offset = index.stop * bytes_per_sample
+
         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)