💾 Archived View for thrig.me › blog › 2023 › 02 › 15 › circle.txt captured on 2023-03-20 at 18:46:50.
-=-=-=-=-=-=-
#!/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; } } }