Clapping music

From XPUB & Lens-Based wiki

... continuing from the sedsongs exercise

See the page on Bash for help with using loops & variables

This is an exercise to create a Bash version of Steve Reich's Clapping Music using Bash and a pipeline using midge and timidity.

If you look at a copy of the musical notation for the piece, you can start to see that in fact it's based on a pattern, and could be easily (better?) expressed as an algorithm, namely a loop in which a pattern slowly shifts.

To start, create the first two bars "by hand" as a midge file:

@head {
    $tempo 120
    $time_sig 4/4
}
@body {
    @channel 1 {
        $patch 1
        $length 16
        $octave 4
        $pan 0

    # the main pattern (3, 2, 1, 2 claps with rests in between)
    c c c r c c r c r c c r
    # the first performer simply repeats the pattern...
    c c c r c c r c r c c r

    }
    @channel 2 {
        $patch 1
        $length 16
        $octave 5
        $pan 127

    # the 2nd performer starts with the main pattern
    c c c r c c r c r c c r
    # .. and (eventually) shifts the pattern
    c c r c c r c r c c r c

    }
}

Now, we convert the midge file to a minimal bash script that does nothing but outputting the text (using a heredoc and cat). Note the need to backslash the $'s.

cat << END
@head {
    \$tempo 120
    \$time_sig 4/4
}
@body {
    @channel 1 {
        \$patch 1
        \$length 16
        \$octave 4
        \$pan 0

    c c c r c c r c r c c r
    c c c r c c r c r c c r

    }
    @channel 2 {
        \$patch 1
        \$length 16
        \$octave 5
        \$pan 127

    c c c r c c r c r c c r
    c c r c c r c r c c r c

    }
}
END

We will make use of a simple counting loop (see Bash#Loops) ...

for ((i=0; i<13;i++))
do
# ...
done

Shifting the pattern

Consider what happens when the pattern shifts:

xxx_xx_x_xx_ ==> 
xx_xx_x_xx_x

or as Bash:

pat="xxx_xx_x_xx_"
pat="xx_xx_x_xx_x"

Now consider what one does "by hand" to make this change:

Shift.png

In code you can express the shift by:

pat=${pat:1}${pat:0:1}

or, more minimally (leaving out the 0 offset):

pat=${pat:1}${pat::1}

Final code

A final script:

pat="xxx_xx_x_xx_"

cat << end
@head {
    \$tempo 120
    \$time_sig 4/4
}
@body {
    @channel 1 {
        \$patch 1
        \$length 16
        \$octave 4
        \$pan 0
end

for ((bar=0; bar<13; bar++))
do
    for ((repeat=0; repeat<12; repeat++))
    do
        echo $pat | sed 's/x/c /g; s/_/r /g'
    done
done

cat << end
    }
    @channel 2 {
        \$patch 1
        \$length 16
        \$octave 5
        \$pan 127
end
         
for ((bar=0; bar<13; bar++))
do
    for ((repeat=0; repeat<12; repeat++))
    do
        echo $pat | sed 's/x/c /g; s/_/r /g'
    done
    pat=${pat:1}${pat:0:1}
done

cat << end 
    }
}
end

To play the result:

bash clappingmusic.sh | midge -o clappingmusic.mid
timidity clappingmusic.mid

File:Clappingmusic.ogg