bashtris/korobeiniki.sh

123 lines
4.6 KiB
Bash
Executable File

#!/bin/bash
# Korobeiniki v1.1 (October 24, 2020), music for the bashtris game.
# Copyright (C) 2012, 2020 Daniel Suni
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Playback method should be /dev/dsp -type OSS device. If no playback method
# is provided ALSA (aplay) is assumed.
# Thanks to Github user jobbautista9 for ALSA code suggestion
PLAYBACK_METHOD=$1
# /dev/dsp default = 8000 frames per second, 1 byte per frame
declare -r FPS=8000
declare -r VOLUME=$'\xc0' # Max volume = \xff
declare -r MUTE=$'\x80' # Middle of the scale = No volume (\x00 would also be max vol)
# The "notes" look like this:
# oxxxxxxxxxxxxxxxxxxxxxxxxxxxxxoxxxxxxxxxxxxxxxxxxxxxxxxxxxxxoxxxxxxxxxxxxxxxxxxxxxxxxxxxxxoxx...
# ^ ^ ^- Repeat previous sequence ^- Repeat again, et.c.
# |-- "volume byte" |- N "mute bytes", where N is determined by the frequency desired.
#
# Since /dev/dsp uses 8000 fps, the frequency of the volume bytes should be 8000 / tone frequency
# E.g. for an "A" of 440 Hz the byte frequency should be 8000 / 440 ~= 18 , i.e.
# oxxxxxxxxxxxxxxxxxoxxxxxxxxxxxxxxxxxoxxxxxxxxxxxxxxxxxoxxx... et.c.
#
# The total number of bytes in the repeated sequences determines the duration of the note. (8000 bytes = 1s)
#
# Since this method does not use precise values, the notes will be somewhat off-key. This problem
# gets worse with increasing frequencies, as the rounding errors get bigger. With lower frequencies
# it's not really noticable to the untrained ear.
# Notes in hertz
declare -r c0=65.40639
declare -r d0=73.41619
declare -r eb0=77.78175
declare -r f0=87.30706
declare -r g0=97.99886
declare -r ab0=103.8262
declare -r a0=110
declare -r bb0=116.5409
declare -r b0=123.4708
declare -r c=130.8128
declare -r d=146.8324
declare -r eb=155.5635
declare -r e=164.8138
declare -r f=174.6141
declare -r g=195.9977
declare -r ab=207.6523
declare -r a=220
declare -r bb=233.0819
declare -r b=246.9417
declare -r c2=261.6256
declare -r d2=293.6648
declare -r eb2=311.1270
declare -r s=7999 # Silence
# Note durations ha = half, qu = quarter, et.c.
declare -r ha=8
declare -r qu=4
declare -r que=3
declare -r ei=2
declare -r si=1
declare -r ss=0 # Will be translated to a very short non-zero duration.
function note { # $1 = pitch (Hz) $2 = duration (bytes)
mute_bytes_num=$(echo "$FPS / $1 - 1" | bc)
note_bytes="$VOLUME`yes $MUTE | tr -d '\n' | head -c $mute_bytes_num`" # Create 1 oxxx...-sequence
yes $note_bytes | tr -d '\n' | head -c $2 # Create as many bytes of concatenated sequences as needed.
}
# Smaller value = faster tempo
declare -r TEMPO=900
function tune { # $1 = List of notes in the format pitch(Hz):duration(note)
for n in $1 ; do
pitch=`echo $n | sed 's/:.*//'`
duration=`echo $n | sed 's/.*://'`
((duration*=TEMPO))
if [ $duration -eq 0 ] ; then
duration=50
fi
echo -n "`note $pitch $duration`"
done
}
# Korobeiniki is a Russian folk song (and as such, part of the public domain).
# It consists of 2 distinct parts.
tune_a="$g:$qu $d:$ei $eb:$ei $f:$qu $eb:$ei $d:$ei $c:$qu $s:$ss $c:$ei $eb:$ei $g:$qu $f:$ei
$eb:$ei $d:$qu $s:$ss $d:$ei $eb:$ei $f:$qu $g:$qu $eb:$qu $s:$ss $c:$qu $s:$ss $c:$qu $s:$qu
$f:$qu $s:$ss $f:$ei $ab:$ei $c2:$qu $bb:$ei $ab:$ei $g:$qu $s:$ss $g:$ei $eb:$ei $g:$qu $f:$ei
$eb:$ei $d:$qu $s:$ss $d:$ei $eb:$ei $f:$qu $g:$qu $eb:$qu $s:$ss $c:$qu $s:$ss $c:$qu $s:$qu"
tune_b="$g:$ha $eb:$ha $f:$ha $d:$ha $eb:$ha $c:$ha $b0:$ha $d:$ha $s:$ss $g:$ha $eb:$ha
$f:$ha $d:$ha $eb:$qu $g:$qu $c2:$qu $s:$ss $c2:$qu $b:$ha $b:$ha"
cr_tune_a=`tune "$tune_a"`
cr_tune_b=`tune "$tune_b"`
# Allow the parent (game) script to kill the process when it's not needed any more.
trap 'exit 0' SIGUSR2
while true ; do
# Run echo command in a subshell to prevent the sound from going berserk when script exits.
# (This may cause the tune to play until finished, then stop even if script is killed.)
if [ -z $PLAYBACK_METHOD ] ; then
( echo -n "$cr_tune_a$cr_tune_a$cr_tune_b" | aplay ) &>/dev/null &
else
( echo -n "$cr_tune_a$cr_tune_a$cr_tune_b" > $PLAYBACK_METHOD ) &>/dev/null &
fi
wait
done