💾 Archived View for thrig.me › blog › 2023 › 02 › 15 › circle.txt captured on 2023-03-20 at 18:46:50.

View Raw

More Information

-=-=-=-=-=-=-

#!/usr/bin/env perl
#
#   "by the power of Christoffel" -- said He-man, never
#
# generates Christoffel words as beat patterns in some swept angle of
# degrees, selecting for the unique patterns, and generates a MIDI of
# said patterns. there are things to TWEAK
#
# most of these are pretty crummy as rhythms, though there can be good
# bits away from 0 and 90 degrees and probably not at 45 degrees either

use 5.32.0;
use warnings;
use experimental 'signatures';
use Math::Trig;
use Music::RhythmSet 0.05;

# TWEAK
my $angle     = deg2rad(29.5);     # starting angle
my $beatlim   = 16;                # how many beats at most for the lines
my $radius    = 1000;              # this may need fiddling with
my $sweep     = deg2rad(0.001);    # and also this, too
my $sweep_fmt = '%.2f';
my $max_angle = pi / 4;

my $file = 'out.midi';

say "--";

my $npoints = 0;
my ( $bigpattern, %seenxy, %seenpat );
while ( $angle < $max_angle ) {
    my ( $x, $y ) = map { int( $radius * $_ ) } cos $angle, sin $angle;
    my $p = "$x,$y";
    next if $seenxy{$p}++;

    my $pattern = christoffel( $x, $y, $beatlim );
    # 45 degrees can come up short, pad if need be
    my $len = @$pattern;
    if ( $len < $beatlim ) {
        push @$pattern, (0) x ( $beatlim - $len );
    }

    smooth($pattern);

    my $pstr = join '', $pattern->@*;
    next if $seenpat{$pstr}++;

    $npoints++;
    push @$bigpattern, $pattern->@*;

    my $deg = sprintf $sweep_fmt, rad2deg($angle);
    #say join "\t", $deg, $p, $pstr;
    say $pstr;

} continue {
    $angle += $sweep;
}

# TWEAK
Music::RhythmSet->new->add( { next => sub { $bigpattern, 1 }, ttl => 1 }, )
  ->advance(1)->to_midi(
    sustain => 1,
    chan    => 9,
    #patch_change => 0,
    tempo => 640e3,
    track => [ { note => 36, } ]
)->write_to_file($file);

say "ok $npoints $file";

# this is the upper pattern, as that puts a "1" in the leading position
# which is typical for rhythms. borrowed from chsequl.c of
# https://abrazol.com/books/rhythm1/software.html
sub christoffel ( $x, $y, $max = $x + $y ) {
    my @pattern = (1);
    my ( $i, $j, $n ) = ( $x, $y, 0 );
    do {
        ++$n;
        for ( $i = $x, $j = $y; $i != $j and $n < $max; ++$n ) {
            if ( $i > $j ) {
                push @pattern, 1;
                $j += $y;
            } else {
                push @pattern, 0;
                $i += $x;
            }
        }
        if ( $i == $j and $n < $max ) {
            push @pattern, 0;
            ++$n;
        }
    } while ( $n < $max );
    return \@pattern;
}

# avoid repeated beats, e.g. 11101000 becomes 10001000
sub smooth ($pattern) {
    for my $i ( reverse 1 .. $#$pattern ) {
        if ( $pattern->[$i] and $pattern->[ $i - 1 ] ) {
            $pattern->[$i] = 0;
        }
    }
}