Repository: music-computing/amads

Issue:
Slice should inherit from Sequence
Thanks @diwangislucky for the suggestion!

Hints from issue comments:

Solution patch:
diff --git a/amads/algorithms/slice/salami.py b/amads/algorithms/slice/salami.py
index 4827a231..ebb44010 100644
--- a/amads/algorithms/slice/salami.py
+++ b/amads/algorithms/slice/salami.py
@@ -194,10 +194,10 @@ def salami_slice(
 
             slices.append(
                 Slice(
-                    notes=notes,
+                    content=notes,
                     original_notes=timepoint.sounding_notes,
-                    start=slice_start,
-                    end=slice_end,
+                    delta=slice_start,
+                    duration=slice_end - slice_start,
                 )
             )
 
diff --git a/amads/algorithms/slice/slice.py b/amads/algorithms/slice/slice.py
index 402a487d..25ef1e0b 100644
--- a/amads/algorithms/slice/slice.py
+++ b/amads/algorithms/slice/slice.py
@@ -1,9 +1,9 @@
 from typing import List
 
-from ...core.basics import Note
+from ...core.basics import Note, Sequence
 
 
-class Slice:
+class Slice(Sequence):
     """A slice of a musical score between two timepoints.
 
     This is the base class for different slicing algorithms like salami slicing and
@@ -13,67 +13,23 @@ class Slice:
 
     Parameters
     ----------
-    notes : List[Note]
+    content : List[Note]
         The notes in this slice, with durations truncated to fit within the slice
         boundaries if necessary
     original_notes : List[Note]
         The original unmodified notes from which the slice notes were derived
-    start : float
-        The start time of the slice
-    end : float
-        The end time of the slice
+    delta : float
+        The start time offset of the slice
+    duration : float
+        The duration of the slice
     """
 
     def __init__(
         self,
-        notes: List[Note],
+        content: List[Note],
         original_notes: List[Note],
-        start: float,
-        end: float,
+        delta: float = 0,
+        duration: float = 0,
     ):
-        self.notes = notes
+        super().__init__(delta=delta, duration=duration, content=content)
         self.original_notes = original_notes
-        self.start = start
-        self.end = end
-
-    def __iter__(self):
-        """Iterate over the notes in this slice.
-
-        Returns
-        -------
-        Iterator[Note]
-            Iterator over the notes
-        """
-        return iter(self.notes)
-
-    def __len__(self):
-        """Get the number of notes in this slice.
-
-        Returns
-        -------
-        int
-            Number of notes
-        """
-        return len(self.notes)
-
-    @property
-    def duration(self):
-        """Get the duration of this slice.
-
-        Returns
-        -------
-        float
-            Duration in time units
-        """
-        return self.end - self.start
-
-    @property
-    def is_empty(self):
-        """Check if this slice contains any notes.
-
-        Returns
-        -------
-        bool
-            True if the slice contains no notes
-        """
-        return len(self.notes) == 0
diff --git a/amads/algorithms/slice/window.py b/amads/algorithms/slice/window.py
index 74dd932d..68c83881 100644
--- a/amads/algorithms/slice/window.py
+++ b/amads/algorithms/slice/window.py
@@ -98,7 +98,10 @@ def __init__(
         self.skip = skip
 
         super().__init__(
-            notes=notes, original_notes=original_notes, start=start, end=end
+            content=notes,
+            original_notes=original_notes,
+            delta=start,
+            duration=size,
         )
 
 
diff --git a/examples/plot_sliding_window.py b/examples/plot_sliding_window.py
index db434160..79f7a2a5 100644
--- a/examples/plot_sliding_window.py
+++ b/examples/plot_sliding_window.py
@@ -51,12 +51,13 @@
 mean_pitch_heights = []
 for window in windows:
     times.append(window.time)
-    if not window.notes:
+
+    if len(window.content) == 0:
         mean_pitch_heights.append(None)
         continue
 
-    total_duration = sum(note.duration for note in window.notes)
-    weighted_sum = sum(note.keynum * note.duration for note in window.notes)
+    total_duration = sum(note.duration for note in window.content)
+    weighted_sum = sum(note.keynum * note.duration for note in window.content)
     mean_pitch_heights.append(weighted_sum / total_duration)
 
 # %%
diff --git a/pyproject.toml b/pyproject.toml
index 73e37081..1c54481a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,6 +21,7 @@ dependencies = [
     "numpy",
     "pandas",
     "matplotlib",
+    "scipy<1.12.0",
     "partitura",
     "setuptools",  # required for partitura
     "music21",


Test patch:
diff --git a/tests/test_salami_slice.py b/tests/test_salami_slice.py
index 21b747ab..aa5d6505 100644
--- a/tests/test_salami_slice.py
+++ b/tests/test_salami_slice.py
@@ -12,7 +12,7 @@ def test_salami_slice_twochan(twochan_score):
     chords = salami_slice(twochan_score)
     assert len(chords) == 16
 
-    pitches = [[int(p.keynum) for p in c] for c in chords]
+    pitches = [[int(p.keynum) for p in c.content] for c in chords]
     assert pitches == [
         [43, 67],
         [43, 64],
diff --git a/tests/test_sliding_window.py b/tests/test_sliding_window.py
index 7aa14c04..559c99dd 100644
--- a/tests/test_sliding_window.py
+++ b/tests/test_sliding_window.py
@@ -27,13 +27,13 @@ def test_sliding_window(twochan_score: Score, twochan_notes: Iterable[Note]):
     first_slice = windows[0]
     assert first_slice.start == approx(-4.0)
     assert first_slice.end == approx(0.0)
-    assert first_slice.is_empty
+    assert len(first_slice.content) == 0
 
     last_slice = windows[-1]
     assert last_slice.start == approx(last_note_off, abs=step * 3)
-    assert last_slice.is_empty
+    assert len(last_slice.content) == 0
 
     # The second slice should include the score's opening two notes
-    assert len(windows[1].notes) == 2
-    assert windows[1].notes[0].keynum == 67 - 2 * 12
-    assert windows[1].notes[1].keynum == 67
+    assert len(windows[1].content) == 2
+    assert windows[1].content[0].keynum == 67 - 2 * 12
+    assert windows[1].content[1].keynum == 67