#! /usr/bin/perl -w # USAGE: vex.pl [repetitions] # generates a realization of satie's vexations in a midi file # from the composer [satie]: # to play this motif 840 times in succession # it would be advisable to prepare oneself beforehand, # in the deepest silence, # by serious immobilities. use strict; use MIDI::Simple; my $loopCount = 0; if ( defined $ARGV[0] ) { if ( $ARGV[0] =~ /^\d+$/ ) { if ( ( $ARGV[0] > 15 ) && ( $ARGV[0] < 10000 ) ) { $loopCount = $ARGV[0] - 16; } else { die "repetitions out of range [16-9999]: $ARGV[0]"; } } else { die "non-integer passed in as repetitions: $ARGV[0]"; } } my @cantus = ( 60, 57, 61, 58, 63, 55, 62, 60, 63, 54, 61, 53, 59, 54, 63, 59, 64, 64, undef ); my @voice0 = ( 63, 65, 64, 67, 66, 64, 65, 69, 66, 63, 64, 63, 62, 63, 66, 68, 67, 67, undef ); my @voice1 = ( 69, 73, 70, 73, 72, 70, 71, 75, 72, 69, 70, 69, 68, 69, 72, 74, 73, 73, undef ); my @durations = ( 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 2, 1 ); my $midi = MIDI::Simple->new_score (); $midi->copyright_text_event ( 'attribution share alike' ); $midi->noop ( 'c1' ); &vexBeginning ( $midi ); if ( $loopCount ) { &vexMiddle ( $midi, $loopCount ); } &vexEnd ( $midi ); $midi->text_event ( 'erikSatie -> johnCage -> johnSaylor' ); my $filename = sprintf ( "vex_%04d.mid", $loopCount + 16 ); $midi->write_score ( $filename ); print "$filename written!\n\n"; exit; sub vexBeginning { my $midi = shift; if ( ! defined $midi ) { die 'undefined arg passed in' } my $tempo = 60; my $patch = 1; $midi->set_tempo ( int ( 60_000_000 / $tempo ) ); $midi->patch_change ( 1, $patch ); my $velocity = 96; my ( $melody, $harmony0, $harmony1, $duration, $durationExtra ); my $loop = 0; while ( $loop < 10 ) { my $loopVar = $loop % 5; my $c; for ( $c = 0; $c < scalar @cantus; $c++ ) { $duration = $durations[ $c ] * 48; if ( defined $cantus[ $c ] ) { $melody = $cantus[ $c ]; $harmony0 = $voice0[ $c ]; $harmony1 = $voice1[ $c ]; } else { $midi->r ( 'd' . $duration ); next; } # extend pulse & decrease volume arithmetically # change instrument transpose out # 2nd set if ( $loop > 4 ) { $durationExtra += 1; $duration += $durationExtra; $velocity -= 1; my $rnd = int rand 3; if ( ! $rnd ) { $midi->patch_change ( 1, &pickInstrument ); } my ( $intervalOffset, $range ); if ( $loop == 5 ) { $intervalOffset = 12; $range = 2; } elsif ( $loop == 6 ) { $intervalOffset = 6; $range = 3; } elsif ( $loop == 7 ) { $intervalOffset = 3; $range = 4; } elsif ( $loop == 8 ) { $intervalOffset = 3; $range = 6; } elsif ( $loop == 9 ) { $intervalOffset = 1; $range = 8; } $melody = &modifyPitch ( $melody, $intervalOffset, $range ); $harmony0 = &modifyPitch ( $harmony0, $intervalOffset, $range ); $harmony1 = &modifyPitch ( $harmony1, $intervalOffset, $range ); } # solo cantus if ( ( $loopVar == 0 ) || ( $loopVar == 2 ) || ( $loopVar == 4 ) ) { $midi->n ( 'n' . $melody, 'd' . $duration, 'v' . $velocity ); } elsif ( $loopVar == 1 ) { $midi->n ( 'n' . $melody, 'n' . $harmony0, 'n' . $harmony1, 'd' . $duration, 'v' . $velocity ); } elsif ( $loopVar == 3 ) { $harmony0 = ( $harmony0 - 12 ) % 128; $midi->n ( 'n' . $melody, 'n' . $harmony0, 'n' . $harmony1, 'd' . $duration, 'v' . $velocity ); } else { warn 'skipping unknown looping control [$loop, $loop % 5, $loopVar]: ', join ( ', ', $loop, $loop % 5, $loopVar ); next; } } $loop += 1; } return $loop; } sub vexMiddle { my $midi = shift; my $loopCount = shift; if ( ! defined $midi ) { die 'undefined $midi arg passed in' } if ( ! defined $loopCount ) { die 'undefined $loopCount arg passed in' } if ( $loopCount !~ /^\d+$/ ) { die 'loopCount arg not an integer' } my ( $melody, $harmony0, $harmony1, $duration, $rnd ); my $velocity = 64; my $velocityData = { max => 112, min => 48, direction => -1, range => 0.05, current => 96 }; my $tempoData = { max => 192, min => 24, direction => 1, range => 0.12, current => 40 }; my @intervalOffsets = ( 12, 6, 3, 1 ); $midi->set_tempo ( int ( 60_000_000 / $tempoData->{current} ) ); my $patch = &pickInstrument (); $midi->patch_change ( 1, $patch ); my $randomInstrument = 0; my $durationExtra = { unit => 0, tally => 0 }; my $rangeShift = 0; my $loop = 0; while ( $loop < $loopCount ) { my $loopVar = $loop % 5; ## per set changes go here if ( ! $loopVar ) { $rnd = int rand ( 7 ); if ( $rnd ) { # if 0, pick random instrument $rnd = int rand ( 11 ); if ( $rnd ) { $randomInstrument = 0; $patch = &pickInstrument ( $loop ); } else { $randomInstrument = 1; $patch = &pickInstrument ( ); } # patch change actually happens below in per loop section } $rnd = int rand ( 5 ); if ( $rnd ) { &modifyParam ( $tempoData ); $midi->set_tempo ( int ( 60_000_000 / $tempoData->{modified} ) ); $tempoData->{current} = delete $tempoData->{modified}; } } ## per loop changes go here $rnd = int rand ( 11 ); if ( ! $rnd ) { $durationExtra->{unit} = ( int rand ( 5 ) ) - 2; } else { $durationExtra = { unit => 0, tally => 0 }; } $rnd = int rand ( 5 ); if ( ! $rnd ) { # 4 octaves [up or down] possible $rangeShift += ( ( int rand ( 9 ) ) - 4 ) * 12; } else { $rangeShift = 0; } my $patchData = { orig => $patch, offsetFromStart => $patch % 8 }; if ( ! $randomInstrument ) { $rnd = int rand ( 2 ); if ( $rnd ) { $patchData->{startOfFamily} = $patch - $patchData->{offsetFromStart}; $patchData->{modified} = ( int rand ( 8 ) ) + $patchData->{startOfFamily}; $midi->patch_change ( 1, $patchData->{modified} ); } } my $rndLimit; my $c; for ( $c = 0; $c < scalar @cantus; $c++ ) { $duration = $durations[ $c ] * 48; if ( $durationExtra->{unit} != 0 ) { $durationExtra->{tally} += $durationExtra->{unit}; $duration += $durationExtra->{tally}; if ( $duration < 6 ) { $duration = 6; } } if ( defined $cantus[ $c ] ) { $melody = ( $cantus[ $c ] + $rangeShift ) % 128; $harmony0 = ( $voice0[ $c ] + $rangeShift ) % 128; $harmony1 = ( $voice1[ $c ] + $rangeShift ) % 128; } else { $midi->r ( 'd' . $duration ); next; } ## per note changes go here if ( $randomInstrument ) { $rnd = int rand ( 5 ); if ( ! $rnd ) { my $inst = &pickInstrument (); $midi->patch_change ( 1, $inst ); } } $rndLimit = sprintf ( "%d", ( $loopCount / ( $loop + 1 ) ) * 19 ); $rnd = int rand ( $rndLimit ); if ( ! $rnd ) { $rnd = ( int rand ( 3 ) ) + 1; $duration *= $rnd; } $rnd = int rand ( 7 ); if ( ! $rnd ) { &modifyParam ( $velocityData ); $velocity = $velocityData->{modified}; $velocityData->{current} = delete $velocityData->{modified}; } $rnd = int rand ( 13 ); # punch note up or down if ( ! $rnd ) { my $punch = ( int rand ( 49 ) ) - 24; $velocity += $punch; } $velocity = $velocity % 128; # moves toward 1 as loops continue $rndLimit = sprintf ( "%d", $loopCount / ( $loop + 1 ) ); $rnd = int rand ( $rndLimit ); if ( ! $rnd ) { $rndLimit = sprintf ( "%d", ( $loop * ( scalar @intervalOffsets ) ) / $loopCount ); my $idx = int rand ( $rndLimit ); $rndLimit = sprintf ( "%d", ( $loop * 9 ) / $loopCount ); my $range = int rand ( $rndLimit ); if ( $range ) { $melody = &modifyPitch ( $melody, $intervalOffsets[ $idx ], $range ); $rnd = int rand ( 3 ); if ( $rnd ) { $harmony0 = &modifyPitch ( $harmony0, $intervalOffsets[ $idx ], $range ); $rnd = int rand ( 5 ); if ( $rnd ) { $harmony1 = &modifyPitch ( $harmony1, $intervalOffsets[ $idx ], $range ); } } } } # solo cantus if ( ( $loopVar == 0 ) || ( $loopVar == 2 ) || ( $loopVar == 4 ) ) { $midi->n ( 'n' . $melody, 'd' . $duration, 'v' . $velocity ); } elsif ( $loopVar == 1 ) { $midi->n ( 'n' . $melody, 'n' . $harmony0, 'n' . $harmony1, 'd' . $duration, 'v' . $velocity ); } elsif ( $loopVar == 3 ) { $harmony0 = ( $harmony0 - 12 ) % 128; $midi->n ( 'n' . $melody, 'n' . $harmony0, 'n' . $harmony1, 'd' . $duration, 'v' . $velocity ); } else { warn 'skipping unknown looping control [$loop, $loop % 5, $loopVar]: ', join ( ', ', $loop, $loop % 5, $loopVar ); next; } } $loop += 1; } return $loop; } sub vexEnd { my $midi = shift; if ( ! defined $midi ) { die 'undefined arg passed in' } # reset my $tempo = 60; my $patch = 1; $midi->set_tempo ( int ( 60_000_000 / $tempo ) ); $midi->patch_change ( 1, $patch ); # 'silent' loop my $duration = 0; foreach ( @durations ) { $duration += ( $_ * 48 ); } $midi->r ( 'd' . $duration ); my $velocity = 96; my $loop = 0; my ( $melody, $harmony0, $harmony1 ); while ( $loop < 5 ) { my $loopVar = $loop % 5; my $c; for ( $c = 0; $c < scalar @cantus; $c++ ) { $duration = $durations[ $c ] * 48; if ( defined $cantus[ $c ] ) { $melody = $cantus[ $c ]; $harmony0 = $voice0[ $c ]; $harmony1 = $voice1[ $c ]; } else { $midi->r ( 'd' . $duration ); next; } # solo cantus if ( ( $loopVar == 0 ) || ( $loopVar == 2 ) || ( $loopVar == 4 ) ) { $midi->n ( 'n' . $melody, 'd' . $duration, 'v' . $velocity ); } elsif ( $loopVar == 1 ) { $midi->n ( 'n' . $melody, 'n' . $harmony0, 'n' . $harmony1, 'd' . $duration, 'v' . $velocity ); } elsif ( $loopVar == 3 ) { $harmony0 = ( $harmony0 - 12 ) % 128; $midi->n ( 'n' . $melody, 'n' . $harmony0, 'n' . $harmony1, 'd' . $duration, 'v' . $velocity ); } else { warn 'skipping unknown looping control [$loop, $loop % 5, $loopVar]: ', join ( ', ', $loop, $loop % 5, $loopVar ); next; } } $loop += 1; } return $loop; } # $instNum = pickInstrument ( [$loop] ); # optional arg is loop number: for ordered progression # if no loop, just picks random sub pickInstrument { my $loop = shift; my $patch; if ( defined $loop ) { my $instFamStart = ( $loop % 8 ) + 1; my $inst = int rand 8; $patch = ( ( $instFamStart * 16 ) - 15 ) + $inst; # choice of 2 families [8 groups each] of instruments my $rnd = int rand 2; if ( $rnd ) { $patch += 8; } } else { $patch = int rand 128; $patch += 1; } return $patch; } # $newPitchNumber = &modifyPitch ( $oldPitchNumber, $intervalOffset, $range ) # $intervalOffset # value, possible substitutions: # 12, octave # 6, tritone # 4, major third # 3, minor third # 2, whole step # 1, half step # $range is how many possible octaves to offset sub modifyPitch { my $startVal = shift || 0; my $intervalOffset = shift || 0; my $range = shift || 0; my $octaveDivisions = 0; if ( $intervalOffset ) { $octaveDivisions = sprintf ( "%d", 12 / $intervalOffset ); } else { return $startVal; } my $rndLimit = $octaveDivisions * $range; my $rnd = int rand ( $rndLimit ); my $offset = 0; if ( $range > 0 ) { $offset = $rnd - ( int $rndLimit / 2 ); # multiply by reciprocal to get back to chromatic scale $offset *= ( 12 / $octaveDivisions ); } my $transVal = $startVal + $offset; return $transVal % 128; } # &modifyParam ( $hashRef ) # changes value gradually # $hashRef MUST have these keys # max [maximum possible value # min [minimum possible value # direction [integer; # if > 0, value will increase (to max); # if < 0, value will decrease (to min)] # range [multiplier to get scope of new values, # 1 means use complete range of current value # 0 means no change] # current [current value] # returns modified value [also sets 'modified' in hash ref to be new value] sub modifyParam { my $hashRef = shift; if ( ref $hashRef ne 'HASH' ) { warn 'arg is not hash reference: ', ref $hashRef; return undef; } my @requiredKeys = qw( max min direction range current ); foreach my $key ( @requiredKeys ) { if ( ! exists $hashRef->{ $key } ) { warn "$key not found in hash ref, cannot process"; return undef; } } my $rndLimit = sprintf ( "%d", $hashRef->{current} * $hashRef->{range} ); my $offset = int rand ( $rndLimit ); if ( $hashRef->{direction} < 0 ) { $offset *= -1; } my $modified = $hashRef->{current} + $offset; if ( $modified < $hashRef->{min} ) { $modified = $hashRef->{min}; $hashRef->{direction} = 1; } elsif ( $modified > $hashRef->{max} ) { $modified = $hashRef->{max}; $hashRef->{direction} = -1; } $hashRef->{modified} = $modified; return $modified; } # This work is licensed under the Creative Commons Attribution-ShareAlike # License. To view a copy of this license, visit # http://creativecommons.org/licenses/by-sa/2.5/ or send a letter to Creative # Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.