💾 Archived View for thrig.me › blog › 2023 › 12 › 11 › lsystem-to-midi.pl captured on 2024-07-09 at 02:04:35.

View Raw

More Information

⬅️ Previous capture (2023-12-28)

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

#!/usr/bin/perl
# lsystem-to-midi - can we do something musical with the output of a
# L-system? there are as ever things to TWEAK
use 5.36.0;
use MIDI;

# TWEAK output from a L-system
my $s =
  'F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G-F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G-F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G-F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G-F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G-F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G+F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G-F+G+F-G+F+G-F-G+F+G+F-G-F+G-F-G-F+G+F-G+F+G-F-G-F+G+F-G-F+G-F-G';

my $chan = 0;    # MIDI channel
# TWEAK pitch selections
my @table = ( [qw( 52 59 64 71 67 )], [qw( 59 54 62 54 59 )], );
my $ts    = 0;    # table selector
my $i     = 0;    # sub-table index
# TWEAK durations to change between (mostly used by rest())
my @duration = qw( 24 12 );
my $j        = 0;             # duration index
my $leftover = 0;             # MIDI dtime runover
my $notes    = 0;             # how many notes have we burned through?

my @events = ( [ patch_change => 0, $chan, 114 ], );

sub velonoise () { int( rand 4 + rand 4 + rand 4 ) }

sub note ($i) {
    push @events,
      [ note_on => $leftover, $chan, $table[$ts][$i], 101 + velonoise() ],
      [ note_off => $duration[0], $chan, $table[$ts][$i], 0 ];
    $leftover = 0;
    # TWEAK this shift should probably be some divisor of the total
    # number of notes
    if ( ++$notes % 32 == 0 ) {
        $ts ^= 1;
    }
}

sub rest { $leftover += $duration[$j] }

# a 'F' generates a rest (of some changing duration), a 'G' a note (of
# some changing pitch, and different selections of pitches over time),
# and '+' and '-' shift the pitch- and duration- pointer for what F and
# G select on
for my $c ( split '', $s ) {
    if ( $c eq 'F' ) {
        rest();
    } elsif ( $c eq 'G' ) {
        note($i);
    } elsif ( $c eq '+' ) {
        $i = ( $i + 1 ) % $table[$ts]->@*;
    } elsif ( $c eq '-' ) {
        $j = ( $i + 1 ) % @duration;
    }
}

# TWEAK plus a manual coda
push @events,
  [ note_on  => 24, $chan, 71, 107 + velonoise() ],
  [ note_off => 36, $chan, 71, 0 ],
  [ note_on  => 0,  $chan, 71, 107 + velonoise() ],
  [ note_off => 48, $chan, 71, 0 ];

MIDI::Opus->new(
    {   format => 0,
        ticks  => 96,
        tracks => [ MIDI::Track->new( { events => \@events } ) ],
    }
)->write_to_file('out.midi');