💾 Archived View for thrig.me › blog › 2024 › 02 › 17 › twi.pl captured on 2024-05-12 at 15:52:37.

View Raw

More Information

⬅️ Previous capture (2024-03-21)

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

#!/usr/bin/env perl
# twi - send events to a MIDI device
use 5.36.0;
use List::Util  qw[shuffle];
use Time::HiRes qw[usleep];

#srand 640;
warn "SEED ", srand(), "\n";

my $record = shift;

# more clever would be to use MIDI clock events instead of what will be
# somewhat random delays over on the synth, which may interact with
# gates and whatnot there in random ways. but uh TODO
sub MS2US () { 1000 }          # usleep conversion for milliseconds
sub SHORT () { 300 * MS2US }   # how short the pedal note sometimes is
sub LONG ()  { 460 * MS2US }
sub CODA ()  { 100 * MS2US }

sub coinflip () { int rand 2 }

# assuming OpenBSD...
my $dev = shift // '/dev/rmidi0';
open my $fh, '>', $dev or die "open '$dev': $!\n";
binmode $fh;
$fh->autoflush(1);

# also assuming OpenBSD...
my $pid = $record ? record() : undef;
$SIG{__DIE__} = $SIG{INT} = $SIG{TERM} = \&bailout;

# D Hungarian minor, in no way adapted from the "Twilight Zone" theme
my $pedal = 70;
my @ps    = qw[61 62 64 65 68 69];
my $velo  = 96;

# TODO probably want to constrain theme to a certain number of measures,
# even if the number of pitches selected runs short or long? (and
# accounting for the random duration changes, too)
sub theme ( $prior = undef ) {
    my $subdur = 0;
    my @shuf;
    if ( defined $prior ) {
        @shuf = $prior->@*;
    } else {
        @shuf = @ps;
        push @shuf, $shuf[0] if coinflip;
        @shuf = shuffle @ps;
        splice @shuf, rand(@shuf), 1 + int rand 2;
    }
    warn "PS @shuf\n";
    for ( 1 .. 8 ) {
        for my $note (@shuf) {
            $subdur = coinflip;
            printf $fh "\x90%c%c", $pedal, $velo;
            # NOTE these little fiddly delays are to try to provoke
            # glides between the notes or such, depending on what the
            # synth is set to do
            usleep 20;
            printf $fh "\x90%c%c", $note, $velo;
            if ($subdur) {
                usleep SHORT;
                printf $fh "\x80%c%c", $pedal, 0;
                usleep LONG() - SHORT();
            } else {
                usleep LONG;
                printf $fh "\x80%c%c", $pedal, 0;
            }
            usleep 20;
            printf $fh "\x80%c%c", $note, 0;
        }
    }
    return \@shuf;
}

# ABBA, but without the Swedes
my @motifs;
push @motifs, theme for 1 .. 2;
theme($_) for reverse @motifs;
# once you get something not terrible you probably should lock it down.
# see also the srand line up at the top
#@motifs = ( [qw(62 64 69 61)], [qw(68 62 69 65 64)] );
#theme($_) for @motifs;
#theme($_) for reverse @motifs;

# a feeble attempt a coda
if (0) {
    for ( 1 .. 6 ) {
        printf $fh "\x90%c%c", $motifs[0][-1], $velo;
        usleep SHORT;
        printf $fh "\x80%c%c", $motifs[0][-1], $velo;
    }
    $velo += 20;
    if ( $velo > 120 ) {
        warn "tail velo clip";
        $velo = 120;
    }
    my $last = $motifs[0][-1] == 62 ? 69 : 62;
    printf $fh "\x90%c%c", $last, $velo;
    usleep CODA;
    printf $fh "\x80%c%c", $last, $velo;
}

bailout();

sub record {
    warn "recording...\n";
    my $pid = fork // die "fork\n";
    if ( $pid == 0 ) {
        exec qw(/usr/bin/aucat -h wav -o out.wav);
        die "exec failed??\n";
    } else {
        sleep 3;
        return $pid;
    }
}

sub bailout {
    warn "cleanup...\n";
    for my $pitch ( 0 .. 127 ) {
        printf $fh "\x80%c%c", $pitch, 0;
    }
    if ( defined $pid ) {
        my $target = $pid;
        undef $pid;
        sleep 3;
        kill TERM => $target;
        warn "waiting for recording process to exit...\n";
        wait;
    }
    exit;
}