123 lines
4.6 KiB
Bash
Executable File
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
|