Skip to content

Commit

Permalink
upgrades to getElementsByOffset docs; keyword only
Browse files Browse the repository at this point in the history
  • Loading branch information
mscuthbert committed Aug 24, 2018
1 parent 01fa6ac commit d9f94a4
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 57 deletions.
61 changes: 48 additions & 13 deletions music21/stream/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2897,6 +2897,7 @@ def getElementById(self, elementId, classFilter=None):
def getElementsByOffset(self,
offsetStart,
offsetEnd=None,
*,
includeEndBoundary=True,
mustFinishInSpan=False,
mustBeginInSpan=True,
Expand All @@ -2905,7 +2906,7 @@ def getElementsByOffset(self,
'''
Returns a StreamIterator containing all Music21Objects that
are found at a certain offset or within a certain
offset time range (given the start and optional stop values).
offset time range (given the `offsetStart` and (optional) `offsetEnd` values).
There are several attributes that govern how this range is
determined:
Expand Down Expand Up @@ -2945,6 +2946,7 @@ def getElementsByOffset(self,
Setting includeElementsThatEndAtStart to False is useful for zeroLength
searches that set mustBeginInSpan == False to not catch notes that were
playing before the search but that end just before the end of the search type.
This setting is *ignored* for zero-length searches.
See the code for allPlayingWhileSounding for a demonstration.
This chart, and the examples below, demonstrate the various
Expand All @@ -2970,6 +2972,7 @@ def getElementsByOffset(self,
1
>>> out1[0].step
'D'
>>> out2 = st1.getElementsByOffset(1, 3)
>>> len(out2)
1
Expand Down Expand Up @@ -3016,19 +3019,35 @@ def getElementsByOffset(self,
['D']
>>> a = stream.Stream()
>>> n = note.Note('G')
>>> n.quarterLength = .5
>>> a.repeatInsert(n, list(range(8)))
>>> b = stream.Stream()
>>> b.repeatInsert(a, [0, 3, 6])
>>> c = b.getElementsByOffset(2, 6.9)
>>> len(c)
Note how zeroLengthSearches implicitly set includeElementsThatEndAtStart=False.
These two are the same:
>>> out1 = st1.getElementsByOffset(2, mustBeginInSpan=False)
>>> out2 = st1.getElementsByOffset(2, 2, mustBeginInSpan=False)
>>> len(out1) == len(out2) == 1
True
>>> out1.elements[0] is out2.elements[0] is n2
True
But this is different:
>>> out3 = st1.getElementsByOffset(2, 2.1, mustBeginInSpan=False)
>>> len(out3)
2
>>> c = b.flat.getElementsByOffset(2, 6.9)
>>> len(c)
10
>>> out3[0] is n0
True
Explicitly setting includeElementsThatEndAtStart=False does not get the
first note:
>>> out4 = st1.getElementsByOffset(2, 2.1, mustBeginInSpan=False,
... includeElementsThatEndAtStart=False)
>>> len(out4)
1
>>> out4[0] is n2
True
Testing multiple zero-length elements with mustBeginInSpan:
Expand All @@ -3046,6 +3065,20 @@ def getElementsByOffset(self,
OMIT_FROM_DOCS
>>> a = stream.Stream()
>>> n = note.Note('G')
>>> n.quarterLength = .5
>>> a.repeatInsert(n, list(range(8)))
>>> b = stream.Stream()
>>> b.repeatInsert(a, [0, 3, 6])
>>> c = b.getElementsByOffset(2, 6.9)
>>> len(c)
2
>>> c = b.flat.getElementsByOffset(2, 6.9)
>>> len(c)
10
Same test as above, but with floats
>>> out1 = st1.getElementsByOffset(2.0)
Expand Down Expand Up @@ -3094,6 +3127,8 @@ def getElementsByOffset(self,
>>> [el.step for el in out7]
['C', 'D']
Changed in v5.5: all arguments changing behavior are keyword only.
:rtype: Stream
'''
siterator = self.iter.getElementsByOffset(
Expand Down Expand Up @@ -9615,7 +9650,7 @@ def playingWhenAttacked(self, el, elStream=None):
is of the same class, then the first element encountered is
returned. For more complex usages, use allPlayingWhileSounding.
Returns None if no elements fit the bill.
Returns None if no elements fit the bill.
The optional elStream is the stream in which el is found.
If provided, el's offset
Expand Down
55 changes: 30 additions & 25 deletions music21/stream/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,12 +362,21 @@ class OffsetFilter(StreamFilter):
see iterator.getElementsByOffset()
Finds elements that match a given offset range.
Changed in v5.5 -- all arguments except offsetStart and offsetEnd are keyword only.
'''

derivationStr = 'getElementsByOffset'

def __init__(self, offsetStart=0.0, offsetEnd=None,
includeEndBoundary=True, mustFinishInSpan=False,
mustBeginInSpan=True, includeElementsThatEndAtStart=True):

def __init__(self,
offsetStart=0.0,
offsetEnd=None,
*,
includeEndBoundary=True,
mustFinishInSpan=False,
mustBeginInSpan=True,
includeElementsThatEndAtStart=True
):
super().__init__()

self.offsetStart = opFrac(offsetStart)
Expand Down Expand Up @@ -404,34 +413,33 @@ def isElementOffsetInRange(self, e, offset, *, stopAfterEnd=False):
element is in the range, not in the range, or (if stopAfterEnd is True) is not
and no future elements will be in the range.
Factored out from __call__ to be used by OffsetHierarchyFilter
Factored out from __call__ to be used by OffsetHierarchyFilter and it's just
a beast. :-)
'''
dur = e.duration

#offset = common.cleanupFloat(offset)

if offset > self.offsetEnd: # anything that ends after the span is definitely out
if offset > self.offsetEnd: # anything that begins after the span is definitely out
if stopAfterEnd:
# if sorted, optimize by breaking after exceeding offsetEnd
# eventually we could do a binary search to speed up...
raise StopIteration
else:
return False

dur = e.duration

elementEnd = opFrac(offset + dur.quarterLength)
if elementEnd < self.offsetStart:
# anything that finishes before the span ends is definitely out
return False

# some part of the element is at least touching some part of span.
# all the simple cases done! Now need to filter out those that
# are border cases depending on settings

if dur.quarterLength == 0:
elementIsZeroLength = True
else:
elementIsZeroLength = False


# all the simple cases done! Now need to filter out those that
# are border cases depending on settings

if self.zeroLengthSearch is True and elementIsZeroLength is True:
# zero Length Searches -- include all zeroLengthElements
return True
Expand All @@ -450,18 +458,15 @@ def isElementOffsetInRange(self, e, offset, *, stopAfterEnd=False):
if self.mustBeginInSpan is True:
if offset < self.offsetStart:
return False
if self.includeEndBoundary is False:
if offset >= self.offsetEnd:
# >= is unnecessary, should just be ==, but better safe than sorry
return False

if self.mustBeginInSpan is False:
if elementIsZeroLength is False:
if elementEnd == self.offsetEnd and self.zeroLengthSearch is True:
return False
if self.includeEndBoundary is False:
if offset >= self.offsetEnd:
if self.includeEndBoundary is False and offset == self.offsetEnd:
return False
elif (elementIsZeroLength is False
and elementEnd == self.offsetEnd
and self.zeroLengthSearch is True):
return False

if self.includeEndBoundary is False and offset == self.offsetEnd:
return False

if self.includeElementsThatEndAtStart is False and elementEnd == self.offsetStart:
return False
Expand Down
57 changes: 38 additions & 19 deletions music21/stream/iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -853,9 +853,15 @@ def getElementsByGroup(self, groupFilterList):
return self


def getElementsByOffset(self, offsetStart, offsetEnd=None,
includeEndBoundary=True, mustFinishInSpan=False,
mustBeginInSpan=True, includeElementsThatEndAtStart=True):
def getElementsByOffset(
self,
offsetStart,
offsetEnd=None,
*,
includeEndBoundary=True,
mustFinishInSpan=False,
mustBeginInSpan=True,
includeElementsThatEndAtStart=True):
'''
Adds a filter keeping only Music21Objects that
are found at a certain offset or within a certain
Expand Down Expand Up @@ -1002,6 +1008,8 @@ def getElementsByOffset(self, offsetStart, offsetEnd=None,
3
>>> len(list(s.iter.getElementsByOffset(0.0, mustBeginInSpan=False)))
3
Changed in v5.5 -- all arguments changing behavior are keyword only.
OMIT_FROM_DOCS
Expand Down Expand Up @@ -1055,12 +1063,14 @@ def getElementsByOffset(self, offsetStart, offsetEnd=None,
:rtype: StreamIterator
'''
self.addFilter(filters.OffsetFilter(offsetStart,
offsetEnd,
includeEndBoundary,
mustFinishInSpan,
mustBeginInSpan,
includeElementsThatEndAtStart))
self.addFilter(filters.OffsetFilter(
offsetStart,
offsetEnd,
includeEndBoundary=includeEndBoundary,
mustFinishInSpan=mustFinishInSpan,
mustBeginInSpan=mustBeginInSpan,
includeElementsThatEndAtStart=includeElementsThatEndAtStart)
)
return self

#-------------------------------------------------------------
Expand Down Expand Up @@ -1101,7 +1111,7 @@ def notesAndRests(self):
<music21.note.Rest rest>
<music21.note.Note D>
chained filters... (this makes no sense since notes is a subset of notesAndRests
chained filters... (this makes no sense since notes is a subset of notesAndRests)
>>> for el in s.iter.notesAndRests.notes:
... print(el)
Expand Down Expand Up @@ -1548,9 +1558,15 @@ def currentHierarchyOffset(self):
# will still return numbers even if _endElements


def getElementsByOffsetInHierarchy(self, offsetStart, offsetEnd=None,
includeEndBoundary=True, mustFinishInSpan=False,
mustBeginInSpan=True, includeElementsThatEndAtStart=True):
def getElementsByOffsetInHierarchy(
self,
offsetStart,
offsetEnd=None,
*,
includeEndBoundary=True,
mustFinishInSpan=False,
mustBeginInSpan=True,
includeElementsThatEndAtStart=True):
'''
Adds a filter keeping only Music21Objects that
are found at a certain offset or within a certain
Expand All @@ -1574,14 +1590,17 @@ def getElementsByOffsetInHierarchy(self, offsetStart, offsetEnd=None,
<music21.note.Note F#> 9.0 3 Bass
<music21.note.Note B> 9.5 3 Bass
Changed in v5.5 -- all behavior changing options are keyword only.
:rtype: StreamIterator
'''
f = filters.OffsetHierarchyFilter(offsetStart,
offsetEnd,
includeEndBoundary,
mustFinishInSpan,
mustBeginInSpan,
includeElementsThatEndAtStart)
f = filters.OffsetHierarchyFilter(
offsetStart,
offsetEnd,
includeEndBoundary=includeEndBoundary,
mustFinishInSpan=mustFinishInSpan,
mustBeginInSpan=mustBeginInSpan,
includeElementsThatEndAtStart=includeElementsThatEndAtStart)
self.addFilter(f)
return self

Expand Down

0 comments on commit d9f94a4

Please sign in to comment.