Clapping music

From XPUB & Lens-Based wiki


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

See the sedsongs exercise for some background, and the page on Bash for help with using loops & variables.

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

Remember how Bash allows you to make substring selections using a special form:

${variable:offset:length}

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}

Nested loops

When a loop appears inside another loop, it is said to be a nested loop. Note that the inner or nested loop is fastest, in other words that it happens the most often (it loops itself, then loops again for each of the outer loops).

for ((hours=1; hours<=12; hours++))
do

echo ding dong it\'s $hours o\' clock

for ((minutes=0; minutes<60; minutes++))
do
echo the time is $hours hours and $minutes minutes.
done

done

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