MarcTheSpark / scamp

a Suite in Python for Computer-Assisted Music [MIRROR of https://git.sr.ht/~marcevanstein/scamp]
http://scamp.marcevanstein.com
GNU General Public License v3.0
122 stars 11 forks source link

score.py - ValueError: math domain error #5

Open kjcole opened 2 years ago

kjcole commented 2 years ago

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
MarcTheSpark commented 2 years ago

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.

kjcole commented 2 years ago

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()
MarcTheSpark commented 2 years ago

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?

kjcole commented 2 years ago

On Tue, Mar 8, 2022 at 5:15 PM MarcTheSpark @.***> wrote:

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?

Given my gaps in music theory, and my tough time with rhythm, you are almost certainly correct. 😉 I shall ponder: The rhythm produced by the "bones" and bodhran in the playback sounds like what I want it to sound like -- or at least, very close, to my ear -- but there's probably a better idiom for achieving it.