MarcTheSpark/scamp

score.py - ValueError: math domain error

Opened this issue · 4 comments

I was revisiting code that I don't recall giving this error before. I recently updated to SCAMP version 0.8.9.5.

You may recall helping me with a pseudo-Celtic random song generator. That's what's crapping out. It plays the tune, but upon finishing it yields the following when trying to generate the score:

Traceback (most recent call last):
  File "./celtic.py", line 99, in <module>
    main()
  File "./celtic.py", line 93, in main
    performance.to_score(title="Beating the Cat",
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/performance.py", line 1094, in to_score
    return Score.from_performance(
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 934, in from_performance
    return Score.from_quantized_performance(
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 956, in from_quantized_performance
    staff_group = StaffGroup.from_quantized_performance_part(part)
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 1344, in from_quantized_performance_part
    return StaffGroup._from_measure_voice_grid(
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 1559, in _from_measure_voice_grid
    [
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 1560, in <listcomp>
    Staff._from_measure_bins_of_voice_lists(
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 1654, in _from_measure_bins_of_voice_lists
    return cls([Measure.from_list_of_performance_voices(measure_content, time_signature, show_time_signature)
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 1654, in <listcomp>
    return cls([Measure.from_list_of_performance_voices(measure_content, time_signature, show_time_signature)
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 1762, in from_list_of_performance_voices
    voices.append(Voice.from_performance_voice(*voice_content))
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 1926, in from_performance_voice
    processed_contents = Voice._recombine_processed_beats(processed_beats, measure_quantization)
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 2163, in _recombine_processed_beats
    recombined_division_points = Voice._try_all_sub_recombinations(
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 2185, in _try_all_sub_recombinations
    recombo, _ = _get_best_recombination_given_beat_hierarchy(
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 216, in _get_best_recombination_given_beat_hierarchy
    best_subgroup_option, best_subgroup_score = _get_best_subgroup_recombination_option(
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 242, in _get_best_subgroup_recombination_option
    assert all(_is_single_note_viable_grouping(x, max_dots=engraving_settings.max_dots_allowed)
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 242, in <genexpr>
    assert all(_is_single_note_viable_grouping(x, max_dots=engraving_settings.max_dots_allowed)
  File "/home/kjcole/.local/lib/python3.8/site-packages/scamp/score.py", line 175, in _is_single_note_viable_grouping
    if Fraction(math.log2(length_in_subdivisions / dot_multiplier)).limit_denominator().denominator == 1:
ValueError: math domain error

Hey Kevin!

Can you post the script that's not working? It's a little hard to tell without seeing your code, but it looks like somehow it's getting a negative note length or something.

GitHub doesn't like Python attachments. (I suppose I could have just changed the extension, but here it is.)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# "Beating the Cat"
# Decomposed by Kevin Cole <ubuntourist@hacdc.org> 2021.02.19 (kjc)
#
# Tuning info courtesy of http://www.hotpipes.com/tuning.html
#
# Sorta 6/8?
#

from random import choice, randint, shuffle, sample, uniform
from scamp  import *
from scamp_extensions.pitch import Scale

s = Session(tempo=120)

woodblock = s.new_part("woodblock")
bagpipe   = s.new_part("bagpipe")
bodhran   = s.new_part("bodhran",
                       soundfont="soundfonts/Bodhran")  # Bodhran.sf2

#          G4  A4  B4  C#5 D5  E5  F#5 G5  A5
chanter = (67, 69, 71, 73, 74, 76, 78, 79, 81)  # A4 is the "key note"
scale   = Scale.from_pitches(chanter)


def rickity(duration=32, shift=0):
    """Clickity-clackity, rickity-tickity, pseudo-bones"""
    pitches = (  75,   73,   67,   77,   70,   65)
    volumes = ( 0.75,  0.75,  0.75,  1.0,  0.75,  0.75)
    lengths = ((1.0 / 3.0),) * 6  # 0.333333..., 0.333333..., ...
    while current_clock().beat() < duration:
        for pitch, volume, length in zip(pitches, volumes, lengths):
            woodblock.play_note(pitch + shift, volume, length)


def boombah(duration=32):
    """Boom-bah of the bodhran"""
    notes = ((60, 58, 58, 60, 58, 58),
             (60, 58) * 2,
             (60, 58))
    vols  = ((0.75,  0.75,  0.75,  1.0,  0.75,  0.75),
             (1.0,   0.75,) * 2,
             (1.0,   0.75))
    durs  = (((1.0 / 3.0),)             * 6,  # 0.3333..., 0.3333..., ...
             ((2.0 / 3.0), (1.0 / 3.0)) * 2,  # 0.6666..., 0.3333..., 0.6666...
             ((4.0 / 3.0), (2.0 / 3.0)))      # 1.3333..., 0.6666
    wait(4)
    while current_clock().beat() < duration:
        pattern = randint(0, 2)
        for note, vol, dur in zip(notes[pattern],
                                  vols[pattern],
                                  durs[pattern]):
            bodhran.play_note(note, vol, dur)


def pipe(duration=32):
    """The pipes, the pipes, are droning"""
    # Drones chord
    #
    drones = (57, 57, 45)  # A2, A2, A3 (tenor, tenor, bass)
    wait(8)
    drone = bagpipe.start_chord(drones, 0.8)
    wait(4)
    note = choice(chanter)
    while current_clock().beat() < duration:
        dur  = randint(1, 6) * (1.0 / 3.0)
        bagpipe.play_note(note, 1.0, dur)
        leading = note
        while abs(note - leading) in (0, 1, 2, 3, 6, 8, 10, 11, 13, 14, 15):
            note = choice(chanter)

    # Deflate the bag
    #
    drone.end()

    bagpipe.play_note([81, 76], [0.6, 0], 1.0, blocking=False)
    bagpipe.play_note([57, 52], [0.6, 0], 1.0, blocking=False)
    bagpipe.play_note([45, 40], [0.8, 0], 1.0)


def main():
    """The main attraction"""
    s.start_transcribing()
    s.fork(rickity, args=(42, 30,))
    s.fork(boombah, args=(44,))
    s.fork(pipe,    args=(40,))
    s.wait_for_children_to_finish()

    if s.is_transcribing():
        performance = s.stop_transcribing()
        performance.to_score(title="Beating the Cat",
                             composer="Decomposer: Kevin Cole",
                             time_signature="6/8").show_xml()


if __name__ == "__main__":
    main()

Well, I tracked down the source of the issue! You're doing something very strange by accident here: the boombah part is using triplets, but in the context of 6/8 time. This creates a weird situation for the quantizer, since it's trying to make sense of 4/3 of a quarter note within dotted-quarter-note beats. I need to fix something here for sure, but I'm guessing that the triplets may not be what you're really intending?