summaryrefslogtreecommitdiff
path: root/src/unheard/interval.clj
blob: c10161c700eb4abfd4149d702b52849cff0877f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
(ns unheard.interval
  "Custom IInterval implementation for Clojure ratios with arbitrary values."
  (:import [com.brein.time.timeintervals.intervals IInterval]
           [com.brein.time.timeintervals.collections ListIntervalCollection]))

(deftype RatioValueInterval [start end value]
  IInterval
  ;; HACK: coerce start to long to work around
  ;; class clojure.lang.BigInt cannot be cast to class java.lang.Comparable
    (getNormStart [_] (if (instance? clojure.lang.BigInt start)
                        (long start)
                        start))
  ;; HACK: coerce start to long to work around
  ;; class clojure.lang.BigInt cannot be cast to class java.lang.Comparable
    (getNormEnd [_] (if (instance? clojure.lang.BigInt end)
                      (long end)
                      end))
    (getUniqueIdentifier [_] (str "[" start "," end "]"))
    (compareTo [_ other]
      (let [start-cmp (compare start (.getNormStart ^RatioValueInterval other))]
        (if (zero? start-cmp)
          (compare end (.getNormEnd ^RatioValueInterval other))
          start-cmp)))
  Object
    (toString [_] (str "[" start ", " end "] -> " value)))

;; Public API

(defn ratio-interval
  "Create a RatioValueInterval with ratio boundaries and arbitrary value.

  Args:
    start - Clojure ratio for interval start (inclusive)
    end - Clojure ratio for interval end (inclusive)
    value - Arbitrary data to associate with interval

  Returns:
    Instance of RatioValueInterval implementing IInterval<Ratio>

  Example:
    (ratio-interval 1/4 3/4 {:note :C :velocity 64})"
  [start end value]
  (->RatioValueInterval start end value))

(defn interval-value
  "Get the value associated with a RatioValueInterval.

  Args:
    interval - A RatioValueInterval instance

  Returns:
    The value stored in the interval"
  [^RatioValueInterval interval]
  (.value interval))

(defn overlaps?
  "Check if two intervals overlap.

  Two intervals [a,b] and [c,d] overlap if max(a,c) <= min(b,d)"
  [^RatioValueInterval iv1 ^RatioValueInterval iv2]
  (let [start1 (.getNormStart iv1)
        end1 (.getNormEnd iv1)
        start2 (.getNormStart iv2)
        end2 (.getNormEnd iv2)]
    (<= (max (compare start1 start2)) (min (compare end1 end2)))))

(defn find-overlaps
  "Find all intervals in a collection that overlap with the query interval.

  Args:
    coll - A collection of intervals (any Iterable)
    query - The interval to query for overlaps

  Returns:
    A sequence of intervals that overlap with the query"
  [coll ^RatioValueInterval query]
  (let [q-start (.getNormStart query)
        q-end (.getNormEnd query)]
    (filter (fn [^RatioValueInterval iv]
              (let [start (.getNormStart iv)
                    end (.getNormEnd iv)]
                ;; Intervals overlap if: start <= q-end AND end >= q-start
                (and (<= (compare start q-end) 0)
                     (>= (compare end q-start) 0))))
      coll)))

(defn create-ratio-interval-collection
  "Create a simple ListIntervalCollection for RatioValueInterval instances.

  This uses a simple list-based collection. Use find-overlaps to query for
  overlapping intervals.

  Returns:
    Configured IntervalCollection ready to accept ratio-interval instances"
  []
  (ListIntervalCollection.))