-
Notifications
You must be signed in to change notification settings - Fork 406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Minimize gaps produced by quantization algorithm #1540
Changes from 5 commits
98533df
f64ea43
517f046
fe5186a
d4ebe55
a7135d3
1ea8840
44450c4
65692c8
5c232c9
0206700
18b750f
df97c67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4026,7 +4026,7 @@ def procCompare(srcOffset, srcDur, dstOffset, dstDur, divList): | |
for i in range(len(srcDur)): | ||
n = note.Note() | ||
n.quarterLength = srcDur[i] | ||
s.insert(srcOffset[i], n) | ||
s.insert(srcOffset[i], n, ignoreSort=True) | ||
|
||
s.quantize(divList, processOffsets=True, processDurations=True, inPlace=True) | ||
|
||
|
@@ -4071,6 +4071,18 @@ def procCompare(srcOffset, srcDur, dstOffset, dstDur, divList): | |
|
||
[8, 6]) # snap to 0.125 and 0.1666666 | ||
|
||
# User-reported example: contains overlap and tiny gaps | ||
# Parsing with fewer gaps in v.9, as long as stream is sorted | ||
# https://github.com/cuthbertLab/music21/issues/1536 | ||
procCompare([2.016, 2.026, 2.333, 2.646, 3.0, 3.323, 3.651], | ||
[0.123, 0.656, 0.104, 0.094, 0.146, 0.099, 0.141], | ||
|
||
[2, 2, F('7/3'), F('8/3'), 3.0, F('10/3'), F('11/3')], | ||
[F('1/3'), F('2/3'), F('1/3'), F('1/3'), | ||
F('1/3'), F('1/3'), 0.25], | ||
|
||
[4, 3]) | ||
|
||
def testQuantizeMinimumDuration(self): | ||
''' | ||
Notes (not rests!) of nonzero duration should retain a nonzero | ||
|
@@ -4726,6 +4738,13 @@ def testSortAndAutoSort(self): | |
self.assertEqual([(0.0, 2), (0.0, 30), (5.0, 25), (8.0, 10), (10.0, 2), | ||
(15.0, 10), (20.0, 2), (22.0, 1.0)], match) | ||
|
||
def testInsertIgnoreSort(self): | ||
'''A sorted stream does not become unsorted when ignoreSort=True.''' | ||
s = Stream() | ||
s.repeatAppend(note.Note(), 4) | ||
s.insert(1, tempo.MetronomeMark(), ignoreSort=True) | ||
self.assertTrue(s.isSorted) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems strange to me -- can you explain this? The Stream is no longer sorted -- why is it reporting True for isSorted? I don't think I like this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I think I just botched the setup of the test case. Inserting something at the end would make a more clear test case, I'll fix it. What motivated this: the docs for insert say:
But this test fails on master, which is to say, providing ignoreSort=True does change the sort status, even if the stream stays sorted (as I'll update the test to actually demonstrate!) This is because insert() was unconditionally calling coreElementsChanged with clearIsSorted=True.
Yeah, maybe ignoreSort needs to become exposed on coreInsert only? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How I ended up here was:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yeah, I think that's a much better place. And note that coreInsert still computes and returns whether the stream is guaranteed to be sorted. But we shouldn't expose anything to the users where they could end up with a stream that needs manual sorting unless they set isSorted = False. Feel free to remove. Or just ignore the test and remove calls from this PR and I'll remove ignoreSort in insert later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverted changes and just went with an explicit sort 👍 . I think I misdiagnosed exactly what was unsorted in the first place anyway. |
||
|
||
def testMakeChordsBuiltA(self): | ||
# test with equal durations | ||
pitchCol = [('C2', 'A2'), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, but generally add new attributes to a namedtuple at the end so anyone using
[0]
still gets the same result.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Unfortunately we could no longer use it for sorting (unless there's something clever I'm not thinking of, like layering a sort function on top, but that's what I took the namedtuple to function as.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally I've used namedtuples because before it was a standard (anonymous) tuple, so people might still be using access by index or destructuring paradigms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, yes that's fine. You could sort with
key=lambda bqm: (bqm[-1], *bqm)
but yeah, too much trouble for backwards compatibility on an internal object.I did though change it to use min -- there's no need to sort the whole thing if you're just getting the minimum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah! That was old code back when the m21 team didn't know its own algorithmic complexity