Thonny and Quevedo Gómez Musical Work

from scamp import *
import random
import math

# ---------------------------------------------
# SCAMP session setup
# ---------------------------------------------
s = Session(100)  # 100 bpm ≈ 8 minutes of generative material

# Define the instruments
piano = s.new_part("piano")
guitar = s.new_part("guitar")
cello = s.new_part("cello")
violin = s.new_part("violin")
flute = s.new_part("flute")
trumpet = s.new_part("trumpet")
timpani = s.new_part("timpani")

# ---------------------------------------------
# Base scales (in semitones from C4)
# ---------------------------------------------
major_scale = [0, 2, 4, 5, 7, 9, 11, 12]
minor_scale = [0, 2, 3, 5, 7, 8, 10, 12]

# ---------------------------------------------
# Prime factorization utility
# ---------------------------------------------
def prime_factors(n):
    """Return the list of prime factors of n."""
    factors = []
    d = 2
    while n > 1:
        while n % d == 0:
            factors.append(d)
            n //= d
        d += 1
        if d * d > n and n > 1:
            factors.append(n)
            break
    return factors

# ---------------------------------------------
# Algebraic "factorization" patterns
# Inspired by Baldor's algebraic forms: 
# (a + b)^2 = a^2 + 2ab + b^2, etc.
# We'll simulate this by creating harmonic & rhythmic relations.
# ---------------------------------------------
def algebraic_pattern(a, b, variation=1):
    """Generate a list of derived numbers based on algebraic factorization ideas."""
    if variation == 1:
        # Quadratic expansion style
        return [a*a, 2*a*b, b*b]
    elif variation == 2:
        # Difference of squares
        return [a+b, a-b]
    elif variation == 3:
        # Cubic relation
        return [a**3, 3*a*a*b, 3*a*b*b, b**3]
    else:
        # Randomized polynomial
        return [a*b + a + b, a*a - b, b*b - a]

# ---------------------------------------------
# Convert numbers into musical notes
# ---------------------------------------------
def number_to_note(base_pitch, scale, number):
    idx = number % len(scale)
    return base_pitch + scale[idx]

# ---------------------------------------------
# Phrase generator for one instrument
# ---------------------------------------------
def generate_phrase(part, scale, base_pitch, numbers, dur_base=0.5, dynamic_shift=0.0):
    """Generate a musical phrase based on number factorization."""
    for n in numbers:
        factors = prime_factors(abs(n)) if n != 0 else [1]
        for f in factors:
            note = number_to_note(base_pitch, scale, f)
            dur = dur_base * (1 + (f % 3) * 0.5)
            vol = 0.4 + (f % 5) * 0.1 + dynamic_shift
            part.play_note(note, vol, dur)

# ---------------------------------------------
# Main composition
# ---------------------------------------------
def composition():
    total_sections = 8  # 8 sections ≈ 8 minutes
    section_duration = 60  # seconds per section
    
    instruments = [piano, guitar, cello, violin, flute, trumpet, timpani]
    base_pitches = [60, 55, 48, 67, 72, 58, 42]
    base_durations = [0.6, 0.7, 0.8, 0.5, 0.4, 0.7, 1.0]
    dynamic_offsets = [0.0, 0.1, 0.15, 0.05, 0.2, 0.25, 0.3]
    
    for section in range(total_sections):
        # Switch between major and minor modes for contrast
        scale = major_scale if section % 2 == 0 else minor_scale
        
        # Generate algebraic source numbers
        a, b = random.randint(3, 12), random.randint(4, 15)
        variation = (section % 4) + 1
        base_numbers = algebraic_pattern(a, b, variation)
        
        # Create additional rhythmic diversity
        numbers = []
        for n in base_numbers:
            numbers += [n + random.randint(-5, 5) for _ in range(6)]
        
        # Each instrument interprets numbers slightly differently
        for i, part in enumerate(instruments):
            s.fork(
                generate_phrase,
                args=(part, scale, base_pitches[i], numbers[::-1] if i % 2 else numbers, base_durations[i], dynamic_offsets[i])
            )
        
        # Keep section gap under 4 seconds
        s.wait(section_duration - 3 + random.uniform(-1, 1))

# ---------------------------------------------
# Run the composition
# ---------------------------------------------
composition()

My new SCAMP_Python work:
Casio2.