Module music_df.humdrum_export.dur_to_kern
Functions
def attempt_to_represent_as_tuple(inp: float)-
Expand source code
def attempt_to_represent_as_tuple(inp: float): """ >>> attempt_to_represent_as_tuple(0.14406779661015978) """ # TODO: (Malcolm 2024-02-29) pass>>> attempt_to_represent_as_tuple(0.14406779661015978) def dur_to_kern(inp: float | int | fractions.Fraction,
offset: float | int | fractions.Fraction,
meter: str | metricker.meter.Meter,
raise_exception_on_unrecognized_duration: bool = False) ‑> list[tuple[float, str]]-
Expand source code
def dur_to_kern( inp: float | int | Fraction, offset: float | int | Fraction, meter: t.Union[str, Meter], raise_exception_on_unrecognized_duration: bool = False, ) -> list[tuple[float, str]]: """ >>> dur_to_kern(10.25, 0.0, "4/4") [(4.0, '1'), (4.0, '1'), (2.0, '2'), (0.25, '16')] >>> dur_to_kern(10.0, 0.25, "4/4") [(0.25, '16'), (0.5, '8'), (1.0, '4'), (2.0, '2'), (4.0, '1'), (2.0, '2'), (0.25, '16')] >>> dur_to_kern(5.5, 2.0, "3/4") [(1.0, '4'), (3.0, '2.'), (1.5, '4.')] >>> dur_to_kern(9.25, 0.5, "9/8") [(0.5, '8'), (0.5, '8'), (1.5, '4.'), (1.5, '4.'), (4.5, '8%9'), (0.75, '8.')] >>> dur_to_kern(0.3333, 0.0, "4/4") [(0.3333, '12')] >>> dur_to_kern(0.1666, 0.0, "4/4") [(0.1666, '24')] >>> dur_to_kern(0.1667, 0.3333, "2/4") [(0.16670000000000001, '24')] >>> dur_to_kern(0.1667, 1.6667, "2/4") [(0.16670000000000007, '24')] >>> dur_to_kern(0.1667, 1.75, "2/4") [(0.16670000000000007, '24')] >>> dur_to_kern(0.1670000000000016, 1.5, "2/4") [(0.1670000000000016, '24')] Following used to give a recursion error, fixed by `split_fives_hack()` >>> dur_to_kern(5.0, 6.0, "6/4") [(3.0, '2.'), (2.0, '2')] """ if isinstance(meter, str): meter = Meter(meter) split_durs = meter.split_at_metric_strong_points( [_Dur(offset, offset + inp)], min_split_dur=0.5 # type:ignore ) # (Malcolm 2023-10-10) I'm not precisely sure what the rationale for applying # split_odd_duration is split_durs = split_durs[:-1] + meter.split_odd_duration( split_durs[-1], min_split_dur=1.0 # type:ignore ) # Hack split_durs = split_durs[:-1] + split_fives_hack(split_durs[-1]) durs = [float(dur.release - dur.onset) for dur in split_durs] # It's possible these will be different output_durs = [] output_kern_durs = [] for d in durs: kern_dur = duration_float_to_recip(d) if kern_dur.startswith("q"): remainder = d temp_offset = offset for divisor in [1, 2, 4, 8, 16, 24, 32, 48, 64, 96]: whole, remainder = divmod(remainder, 1 / divisor) whole /= divisor if whole <= 0.0: continue result1 = dur_to_kern( whole, temp_offset, meter, raise_exception_on_unrecognized_duration, ) output_durs.extend([r[0] for r in result1]) output_kern_durs.extend([r[1] for r in result1]) if remainder < 1e-6: break temp_offset += whole if remainder > 1e-6 and raise_exception_on_unrecognized_duration: raise KernDurError( f"Unrecognized duration {inp} with {offset=} in {meter=}" ) # if d > 1: # whole, frac = divmod(d, 1.0) # if whole <= 0: # continue # result1 = dur_to_kern( # whole, # offset, # meter, # raise_exception_on_unrecognized_duration, # ) # result2 = dur_to_kern( # frac, # offset + whole, # meter, # raise_exception_on_unrecognized_duration, # ) # output_durs.extend([r[0] for r in result1]) # output_durs.extend([r[0] for r in result2]) # output_kern_durs.extend([r[1] for r in result1]) # output_kern_durs.extend([r[1] for r in result2]) # TODO: (Malcolm 2024-02-29) restore # elif raise_exception_on_unrecognized_duration: # raise KernDurError( # f"Unrecognized duration {inp} with {offset=} in {meter=}" # ) # else: # # TODO: (Malcolm 2024-02-28) remove? # output_durs.append(d) # output_kern_durs.append(kern_dur) else: output_durs.append(d) output_kern_durs.append(kern_dur) # kern_durs = [duration_float_to_recip(d) for d in durs] # if raise_exception_on_unrecognized_duration and any( # d.startswith("q") for d in kern_durs # ): # raise KernDurError(f"Unrecognized duration {inp} with {offset=} in {meter=}") return list(zip(output_durs, output_kern_durs))>>> dur_to_kern(10.25, 0.0, "4/4") [(4.0, '1'), (4.0, '1'), (2.0, '2'), (0.25, '16')] >>> dur_to_kern(10.0, 0.25, "4/4") [(0.25, '16'), (0.5, '8'), (1.0, '4'), (2.0, '2'), (4.0, '1'), (2.0, '2'), (0.25, '16')] >>> dur_to_kern(5.5, 2.0, "3/4") [(1.0, '4'), (3.0, '2.'), (1.5, '4.')] >>> dur_to_kern(9.25, 0.5, "9/8") [(0.5, '8'), (0.5, '8'), (1.5, '4.'), (1.5, '4.'), (4.5, '8%9'), (0.75, '8.')] >>> dur_to_kern(0.3333, 0.0, "4/4") [(0.3333, '12')] >>> dur_to_kern(0.1666, 0.0, "4/4") [(0.1666, '24')] >>> dur_to_kern(0.1667, 0.3333, "2/4") [(0.16670000000000001, '24')] >>> dur_to_kern(0.1667, 1.6667, "2/4") [(0.16670000000000007, '24')] >>> dur_to_kern(0.1667, 1.75, "2/4") [(0.16670000000000007, '24')] >>> dur_to_kern(0.1670000000000016, 1.5, "2/4") [(0.1670000000000016, '24')]Following used to give a recursion error, fixed by
split_fives_hack()>>> dur_to_kern(5.0, 6.0, "6/4") [(3.0, '2.'), (2.0, '2')] def duration_float_to_recip(input: float, threshold=0.01) ‑> str-
Expand source code
def duration_float_to_recip(input: float, threshold=0.01) -> str: """ A Python implementation of humlib's Convert::durationFloatToRecip function. Differences: omits timebase parameter (for now, at least). >>> duration_float_to_recip(4.0) '1' >>> duration_float_to_recip(6.0) '1.' >>> duration_float_to_recip(1 / 3) '12' >>> duration_float_to_recip(3 / 4) '8.' >>> duration_float_to_recip(9 / 2) '8%9' >>> duration_float_to_recip(0.3333) '12' >>> duration_float_to_recip(0.333) '12' >>> duration_float_to_recip(0.334) '12' If threshold is set too high the next tests fail. >>> duration_float_to_recip(0.1667) '24' >>> duration_float_to_recip(0.1666) '24' >>> duration_float_to_recip(0.1670) '24' >>> duration_float_to_recip(0.0833) '48' >>> duration_float_to_recip(0.04165) '96' """ if input == 0.0625: output = "64" return output if input == 0.125: output = "32" return output if input == 0.25: output = "16" return output if input == 0.5: output = "8" return output if input == 1.0: output = "4" return output if input == 2.0: output = "2" return output if input == 4.0: output = "1" return output if input == 8.0: output = "0" return output if input == 12.0: output = "0." return output if input == 16.0: output = "00" return output if input == 24.0: output = "00." return output if input == 32.0: output = "000" return output if input == 48.0: output = "000." return output # special case for triplet whole notes: if abs(input - (4.0 * 2.0 / 3.0)) < 0.0001: return "3%2" # special case for triplet breve notes: if abs(input - (4.0 * 4.0 / 3.0)) < 0.0001: return "3%4" # special case for 9/8 full rests if abs(input - (4.0 * 9.0 / 8.0)) < 0.0001: return "8%9" # special case for 9/2 full-measure rest if abs(input - 18.0) < 0.0001: return "2%9" # handle special rounding cases primarily for SCORE which # only stores 4 digits for a duration if math.isclose(input, 1 / 3, abs_tol=0.01): return "12" if math.isclose(input, 1 / 6, abs_tol=0.01): return "24" if math.isclose(input, 1 / 12, abs_tol=0.01): # triplet 32nd note, which has a real duration of 0.0833333 etc. return "48" if math.isclose(input, 1 / 24, abs_tol=0.01): # triplet 64th note, which has a real duration of 0.0833333 etc. return "96" basic = 4.0 / input diff = basic - int(basic) if diff > (1 - threshold): diff = 1.0 - diff basic += diff output = [] if diff < threshold: output.append(str(int(basic))) else: testinput = input / 3.0 * 2.0 basic = 4.0 / testinput diff = basic - int(basic) if diff < threshold: output.append(str(int(basic))) output.append(".") else: testinput = input / 7.0 * 4.0 basic = 4.0 / testinput diff = basic - int(basic) if diff < threshold: output.append(str(int(basic))) output.append("..") else: testinput = input / 15.0 * 4.0 basic = 2.0 / testinput diff = basic - int(basic) if diff < threshold: output.append(str(int(basic))) output.append("...") else: # Don't know what it could be so echo as a grace note. output.append("q") output.append(str(input)) return "".join(output)A Python implementation of humlib's Convert::durationFloatToRecip function.
Differences: omits timebase parameter (for now, at least).
>>> duration_float_to_recip(4.0) '1' >>> duration_float_to_recip(6.0) '1.' >>> duration_float_to_recip(1 / 3) '12' >>> duration_float_to_recip(3 / 4) '8.' >>> duration_float_to_recip(9 / 2) '8%9' >>> duration_float_to_recip(0.3333) '12' >>> duration_float_to_recip(0.333) '12' >>> duration_float_to_recip(0.334) '12'If threshold is set too high the next tests fail.
>>> duration_float_to_recip(0.1667) '24' >>> duration_float_to_recip(0.1666) '24' >>> duration_float_to_recip(0.1670) '24' >>> duration_float_to_recip(0.0833) '48' >>> duration_float_to_recip(0.04165) '96' def split_fives_hack(dur: music_df.humdrum_export.dur_to_kern._Dur) ‑> list[music_df.humdrum_export.dur_to_kern._Dur]-
Expand source code
def split_fives_hack(dur: _Dur) -> list[_Dur]: """ There is an issue where durations of 5, (or of 5/16, etc.), which cannot be represented in notation without a tie, make it through when they start on a downbeat in certain meters, e.g.: >>> six_four = Meter("6/4") >>> six_four.split_at_metric_strong_points([_Dur(0.0, 5.0)]) [_Dur(onset=0.0, release=5.0)] To avoid this circumstance, we just hackily divide them into 3 + 2. >>> split_fives_hack(_Dur(0.0, 5.0)) [_Dur(onset=0.0, release=3.0), _Dur(onset=3.0, release=5.0)] >>> split_fives_hack(_Dur(0.0, 10.0)) [_Dur(onset=0.0, release=6.0), _Dur(onset=6.0, release=10.0)] >>> split_fives_hack(_Dur(0.0, 1.25)) [_Dur(onset=0.0, release=0.75), _Dur(onset=0.75, release=1.25)] """ # TODO: (Malcolm 2023-12-18) better solution than this hack for divisor in [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96]: if math.isclose(math.log(divisor * dur.dur / 5, 2) % 1, 0, abs_tol=1e-9): dur1 = _Dur(dur.onset, dur.onset + 3 / 5 * dur.dur) dur2 = _Dur(dur1.release, dur.release) return [dur1, dur2] return [dur]There is an issue where durations of 5, (or of 5/16, etc.), which cannot be represented in notation without a tie, make it through when they start on a downbeat in certain meters, e.g.:
>>> six_four = Meter("6/4") >>> six_four.split_at_metric_strong_points([_Dur(0.0, 5.0)]) [_Dur(onset=0.0, release=5.0)]To avoid this circumstance, we just hackily divide them into 3 + 2.
>>> split_fives_hack(_Dur(0.0, 5.0)) [_Dur(onset=0.0, release=3.0), _Dur(onset=3.0, release=5.0)] >>> split_fives_hack(_Dur(0.0, 10.0)) [_Dur(onset=0.0, release=6.0), _Dur(onset=6.0, release=10.0)] >>> split_fives_hack(_Dur(0.0, 1.25)) [_Dur(onset=0.0, release=0.75), _Dur(onset=0.75, release=1.25)]
Classes
class KernDurError (*args, **kwargs)-
Expand source code
class KernDurError(Exception): passCommon base class for all non-exit exceptions.
Ancestors
- builtins.Exception
- builtins.BaseException