Clapping music: Difference between revisions
No edit summary |
No edit summary |
||
(18 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
[[Category:Prototyping]] | |||
This is an exercise to create a Bash version of [[wikipedia:Steve Reich|Steve Reich]]'s [[wikipedia:Clapping Music|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 | 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: | To start, create the first two bars "by hand" as a midge file: | ||
Line 81: | Line 81: | ||
done | done | ||
</source> | </source> | ||
== 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: | |||
[[File:Shift.png]] | |||
Remember how Bash allows you to make ''substring'' selections using a [[Bash#Variables|special form]]: | |||
${variable:offset:length} | |||
In code you can express the shift by: | |||
<source lang="bash"> | |||
pat=${pat:1}${pat:0:1} | |||
</source> | |||
or, more minimally (leaving out the 0 offset): | |||
<source lang="bash"> | |||
pat=${pat:1}${pat::1} | |||
</source> | |||
== 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). | |||
<source lang="bash"> | |||
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 | |||
</source> | |||
== Final code == | |||
A final script: | |||
<source lang="bash"> | |||
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 | |||
</source> | |||
To play the result: | |||
<source lang="bash"> | |||
bash clappingmusic.sh | midge -o clappingmusic.mid | |||
timidity clappingmusic.mid | |||
</source> | |||
[[File:Clappingmusic.ogg]] |
Latest revision as of 10:34, 19 October 2010
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:
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