Open Luke-Poeppel opened 3 years ago
Hi, Luke, yes, this sounds useful and low-maintenance to me.
Upward sounds just as useful as down, e.g. to get from 6/8 back "up" to 3/4. Maybe all of the cases you describe are simply one method?
def rebar(self, shorterDenom=True, limitDenom=8, all=False):
(shorter_denom=True
and all=True
would require a limit_denom
on this idea.)
Not certain of this, though. The list/all functionality could be broken out into a second method. What do you think?
UPDATE: maybe in a future enhancement Stream
could eventually have a rebar()
also that flattened the stream, rebarred time signatures using this method and made new measures? 🤔
Ooh... It does sound like it could be useful, but I don't know that rebar() is the right name, because that seems to imply that measures will be changed (which would be a GREAT function).
My concern is that while 4/4 to 2/2 or vice-versa sounds good, and there's no problem with 10/2 going to 5/1 for people who want that, but 6/8 to 3/4 isn't just a change in fraction -- it's instead a change in the underlying metrical structure completely. (fast 6/8 is generally a type of 2/[dotted-quarter]).
I think that a method call that returns a new TS (or changes the current one) is the best approach. I think that the alternativeInterpretations would be used by something like, "Stream.analyze('meter')" -- which would be REALLY useful. :-) but not something to use with this.
Note that for doing the simplest version of this, you could do:
from fractions import Fraction
ts = meter.TimeSignature('6/4')
frac = Fraction(ts.numerator, ts.denominator)
new_ts = meter.TimeSignature(f'{frac.numerator}/{frac.denominator}')
new_ts # returns <music21.meter.TimeSignature 3/2>
Is this better as a method in TS or as a demonstration in the docs?
Thanks for the interesting responses @jacobtylerwalls and @mscuthbert! The comment on changing the metrical structure was a concern that I also had. I'm not quite sure how this interacts with beaming, for example. I ended up using a simple solution (not integrated into m21):
valid_denominators = [1, 2, 4, 8, 16, 32, 64, 128] # in order
def reframe_ts(ts, new_denominator=None):
"""
Function for 'reframing' a ``music21.meter.TimeSignature`` object to a given denominator (or maximally).
:param ts: A music21 TimeSignature object.
:param int new_denominator: The desired new denominator used in the TimeSignature. If not provided, reduces maximally.
:return: A new time signature object with the desired denominator.
:rtype: music21.meter.TimeSignature
>>> from music21.meter import TimeSignature
>>> reframe_ts(TimeSignature("4/16"), new_denominator=8)
<music21.meter.TimeSignature 2/8>
>>> reframe_ts(TimeSignature("4/4"), new_denominator=None)
<music21.meter.TimeSignature 1/1>
>>> reframe_ts(TimeSignature("7/16"))
<music21.meter.TimeSignature 7/16>
"""
numerator = ts.numerator
denominator = ts.denominator
if new_denominator is None:
new_denominator = valid_denominators[0]
else:
assert new_denominator in set(valid_denominators) # Could raise MeterException here.
while numerator % 2 == 0 and denominator > new_denominator:
numerator = numerator / 2
denominator = denominator / 2
reduced_ts_str = f"{int(numerator)}/{int(denominator)}"
return TimeSignature(reduced_ts_str)
I'd personally prefer something a bit more explicit than the Fractions
option, only because it provides the flexibility to choose a new denominator. I'd be happy to submit a PR with a method similar to the above, but if you feel that this would be better as a note in the docs, no problem by me! 😄
Motivation
I'm working on a project that requires reduction of
meter.TimeSignature
objects to the lowest possible denominator (by removing all powers of two). The music21.meter module provides a number of mathematical tools for reduction –– this feature involves applying it directly tometer.TimeSignature
objects (which hasn't been done yet, as far as I can tell).Feature summary I see two possible new methods for
meter.TimeSignature
. Firstly, one that works similar tokey.alternateInterpretations
that returns a list of all possible reductions. For instance,A second possible feature is a method for reducing a TimeSignature object to a given denominator (or maximally by default):
If it seems limiting to only allow reduction (and not increases of powers of 2, e.g. 2/4 -> 4/8), another option is to instead provide, for instance, a
newDenominator
that instead increases the values by powers of 2. The method might then be calledreframe
or something similar.Proposed implementation The implementation is very simple. Use
meter.tools.divisionOptionsFractionsDownward
to generate the appropriate the tuples forratioStrings
. Use all tuples for the first implementation; use the matching tuple for the second. If it seems useful not to limit to reduction you can simply join all downward tuples withtools.divisionOptionsFractionsUpward
.Intent