#!/usr/bin/env perl # primal - generate notes at periodic intervals, the original being the # prime numbers 3 5 7 11 13 at some arbitrary pitch for each interval. use 5.36.0; use MIDI; my $out_file = shift // 'out.midi'; my @beats = qw( 3 5 7 11 13); my @pitch = qw(69 75 79 70 74); # How long to go on for; this might be some number of beats over some # number of measures (e.g. 64) or a least common multiple of the beats. my $max_beat = 3466; my $channel = 0; # MIDI channel my $duration = 60; # minimum MIDI dtime # From the ##forth IRC chat: # < soweli_iki> they aren't primes, but you should try 8-12-15-18-20 # sometime as well if (1) { @beats = qw( 8 12 15 18 20); # For better "mathcore" points we might map the beats to the pitches # (or even a frequency) by some scheme, e.g. by ratio or here to # modulate into the 12-tone pitch set, with an adjustment. # $ perl -E 'say for map { 60 + $_ % 12 } qw(8 12 15 18 20)' # ... # $ atonal-util basic 68 60 63 66 80 # c,d,f,aes # 012111 # 4-27 Dominant seventh chord # c,ees,ges,aes half_prime @pitch = qw(68 60 63 66 80); # The duration usually will need to be pretty long to hear "enough" # of the pattern variations, and might be automatically scaled by a # least common multiple, unless that value gets too large, unless # you're into that sort of thing. $max_beat = 1024; # This will likely need to be fiddled with depending on the # beats involved. $duration = 14; } my $beat = 0; while ( $beat < $max_beat ) { my @active; for my $i ( 0 .. $#beats ) { $active[$i] = $pitch[$i] if $beat % $beats[$i] == 0; } sound( $beat, $duration, @active ) if @active; $beat++; } generate_midi(); { my @last_beat; # when did the last note_on for the pitch fire? my @trackevents; # array references of MIDI events for each pitch sub generate_midi { my $o = MIDI::Opus->new( { format => 1, # assume multiple tracks ticks => 96, tracks => [ map { MIDI::Track->new( { events => $_ } ) } @trackevents ] } ); $o->write_to_file($out_file); } # Generate pitch-to-a-unique-track note events with appropriate # dtime values. sub sound ( $beat, $dtime, @pitches ) { for my $i ( 0 .. $#pitches ) { next unless defined $pitches[$i]; my $previous = $last_beat[$i]; my $now = $beat * $dtime; # Start the MIDI events relative to the first note that sounds. unless ( defined $previous ) { $previous = $now; @last_beat = ($now) x @last_beat; } my $offset = $now - $previous; push @{ $trackevents[$i] }, [ note_on => $offset, $channel, $pitches[$i], velocity() ], [ note_off => $dtime, $channel, $pitches[$i], 0 ]; # Advance plus dtime due to the note_off consuming a beat. # To hold the note for longer one would need more dtime with # the note_off, at the risk of running into or beyond the # next note_on for the pitch. $last_beat[$i] = $now + $dtime; } } # Humanize the loudness a little. Other options here would be to # impose some rhythmic pattern based on 4/4 or whatever time, or at # some period based on the interval the pitch sounds at (e.g. hit # harder every third note for 3, every 5th note for 5, etc). Or the # velocity could have a global adjustment based on a sine wave # unique to each beat, etc. sub velocity { 80 + int( rand 6 + rand 6 + rand 6 ) } }