-
Notifications
You must be signed in to change notification settings - Fork 3
/
korobeiniki.sh
executable file
·122 lines (109 loc) · 4.47 KB
/
korobeiniki.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/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
declare -r d0=73
declare -r eb0=78
declare -r f0=87
declare -r g0=98
declare -r ab0=104
declare -r a0=110
declare -r bb0=117
declare -r b0=123
declare -r c=131
declare -r d=147
declare -r eb=156
declare -r e=165
declare -r f=175
declare -r g=196
declare -r ab=208
declare -r a=220
declare -r bb=233
declare -r b=247
declare -r c2=262
declare -r d2=294
declare -r eb2=311
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=$(($FPS / $1 - 1))
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