#!/bin/bash # rtttl player by Jonas Häggqvist # # Changelog, current version first: # # Version 0.4: # - Optimisation of the main loop # - New version convention: each additional t marks a new version from 0.3 # This means that 0.4 is rttttlplayer # - Add a few quotes around a couple of variables # - Next version will not use Bash but be POSIX correct # - Fix a typo of frequence->frequency # Version 0.3: # - Add caching to ~/.rtttlplayer/[MD5SUM] # Version 0.2: # - Strips spaces from song (avoids some problems) # - Adds octave 4 # Version 0.1: # - First public release # # Based on the following documents: # - http://www.convertyourtone.com/rtttl.html # - http://www.sseyo.com/tao_group/ave/iss/mom/midifile/meta/rtttl/api.html # - http://en.wikipedia.org/wiki/Notes # - http://en.wikipedia.org/wiki/Scientific_pitch_notation # # Copyright (c) 2006, Jonas Häggqvist # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the program nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Beep command. Must end with "-l 0" # For NSLU2, add -e /dev/input/event0 (and load ixp4xx_beeper kernel module) commandline="beep -l 0" case $1 in "-h*"|"--h*") echo "Usage: `basename $0` [rtttlfile] [verbose]" ;; "") rtttl="Bubble Bobble:d=4,o=5,b=125:8a#6,8a6,8g.6,16f6,8a6,8g6,8f6,8d#6,8g6,8f6,16d#6,8d.6,f.6,16d6,16c6,8a#,8c6,8d6,8d#6,8c6,16d6,8d#.6,8f6,8f6,8g6,16a6,8g.6,8f6,8f6,8g6,8a6,8a#6,8a6,8g.6,16f6,8a6,8g6,8f6,8d#6,8g6,8f6,16d#6,8d.6,f.6,16d6,16c6,8a#,8c6,8d6,8d#6,8c6,16d6,8d#.6,8f6,8f6,8g6,16a6,8f.6,8a#.6" ;; *) if [ ! -f "$1" ]; then echo "Usage: `basename $0` rtttlfile [debug]" exit 1 else # Read entire file rtttl=`cat "$1"` fi ;; esac # Default values def_duration=4 def_scale=6 bpm=63 if [ "$2" != "" ]; then echo $rtttl echo fi if [ `which md5sum` ]; then md5sum=`echo $rtttl |md5sum |sed 's/ .*//'` else md5sum="" echo "Note: md5sum not available. caching skipped" fi if [ ! -d ${HOME}/.rtttlplayer -a "$md5sum" != "" ]; then mkdir ${HOME}/.rtttlplayer > /dev/null 2>&1 fi if [ -f ${HOME}/.rtttlplayer/$md5sum ]; then . ${HOME}/.rtttlplayer/$md5sum exit 0 fi # Extract the name name=`echo $rtttl |sed "s/\([^:]*\).*/\1/"` echo "-- $name --" # Extract settings settings=`echo $rtttl |sed "s/[^:]*:\([^:]*\).*/\1/"` # Parse settings for setting in `echo $settings |sed "s/,/ /g"`; do case ${setting%%=*} in d) def_duration=${setting##*=} ;; o) def_scale=${setting##*=} ;; b) bpm=${setting##*=} ;; *) echo "Error: Unknown default value: ${setting}" exit 1; esac done bar=`expr \( 4 \* 60000 \) / $bpm` if [ "$2" != "" ]; then echo "Default duration: $def_duration" echo "Default scale: $def_scale" echo "BPM: $bpm" echo "One bar: $bar ms" echo fi # Precalculate note values dur1=$bar dur2=`expr $bar / 2` dur4=`expr $bar / 4` dur8=`expr $bar / 8` dur16=`expr $bar / 16` dur32=`expr $bar / 32` echo -n "Parsing.. " # The two documents mentioned at the top disagree on the location of the # special-duration marker search="^\(1\|2\|4\|8\|16\|32\)\?\([PpCcDdEeFfGgAaHhBb]#\?\)\(\.\?\)\([45678]\?\)\$" song=`echo $rtttl |sed "s/[^:]*:[^:]*:\(.*\)/\1/" |sed "s/[[:space:]\r]//g"` for command in `echo $song |sed "s/,/ /g"`; do test=`echo $command |sed "s/${search}//"` if [ "$test" != "" ]; then echo "Error: Unrecognised note: \"${command}\"" exit 2 else eval `echo "$command" |sed "s/${search}/duration=\"\1\";note=\"\2\";dotted=\"\3\";scale=\"\4\";/"` if [ "$duration" == "" ]; then duration=$def_duration; fi if [ "$scale" == "" ]; then scale=$def_scale fi if [ "$dotted" == "." ]; then dotted="yes"; else dotted="no"; fi fi if [ "$note" == "p" -o "$note" == "P" ]; then # This is not really a pause, but it works frequency="1" else case $scale in "4") case $note in "C"|"c") frequency="130.8" ;; "C#"|"c#") frequency="138.6" ;; "D"|"d") frequency="146.8" ;; "D#"|"d#") frequency="155.6" ;; "E"|"e") frequency="164.8" ;; "F"|"f") frequency="174.6" ;; "F#"|"f#") frequency="185.0" ;; "G"|"g") frequency="196.0" ;; "G#"|"g#") frequency="207.7" ;; "A"|"a") frequency="220.0" ;; "A#"|"a#") frequency="233.1" ;; "H"|"h"|"B"|"b") frequency="246.9" ;; *) echo "Error: Unknown note $note in $command" exit 4 ;; esac ;; "5") case $note in "C"|"c") frequency="261.6" ;; "C#"|"c#") frequency="277.2" ;; "D"|"d") frequency="293.7" ;; "D#"|"d#") frequency="311.1" ;; "E"|"e") frequency="329.6" ;; "F"|"f") frequency="349.2" ;; "F#"|"f#") frequency="370.0" ;; "G"|"g") frequency="392.0" ;; "G#"|"g#") frequency="415.3" ;; "A"|"a") frequency="440.0" ;; "A#"|"a#") frequency="466.2" ;; "H"|"h"|"B"|"b") frequency="493.9" ;; *) echo "Error: Unknown note $note in $command" exit 4 ;; esac ;; "6") case $note in "C"|"c") frequency="523.3" ;; "C#"|"c#") frequency="554.4" ;; "D"|"d") frequency="587.3" ;; "D#"|"d#") frequency="622.3" ;; "E"|"e") frequency="659.3" ;; "F"|"f") frequency="698.5" ;; "F#"|"f#") frequency="740.0" ;; "G"|"g") frequency="784.0" ;; "G#"|"g#") frequency="830.6" ;; "A"|"a") frequency="880.0" ;; "A#"|"a#") frequency="932.3" ;; "H"|"h"|"B"|"b") frequency="987.8" ;; *) echo "Error: Unknown note $note in $command" exit 4 ;; esac ;; "7") case $note in "C"|"c") frequency="1047" ;; "C#"|"c#") frequency="1109" ;; "D"|"d") frequency="1175" ;; "D#"|"d#") frequency="1245" ;; "E"|"e") frequency="1319" ;; "F"|"f") frequency="1397" ;; "F#"|"f#") frequency="1480" ;; "G"|"g") frequency="1568" ;; "G#"|"g#") frequency="1661" ;; "A"|"a") frequency="1760" ;; "A#"|"a#") frequency="1865" ;; "H"|"h"|"B"|"b") frequency="1976" ;; *) echo "Error: Unknown note $note in $command" exit 4 ;; esac ;; "8") case $note in "C"|"c") frequency="2093" ;; "C#"|"c#") frequency="2217" ;; "D"|"d") frequency="2349" ;; "D#"|"d#") frequency="2489" ;; "E"|"e") frequency="2637" ;; "F"|"f") frequency="2794" ;; "F#"|"f#") frequency="2960" ;; "G"|"g") frequency="3136" ;; "G#"|"g#") frequency="3322" ;; "A"|"a") frequency="3520" ;; "A#"|"a#") frequency="3729" ;; "H"|"h"|"B"|"b") frequency="3951" ;; *) echo "Error: Unknown note $note in $command" exit 4 ;; esac ;; *) # This shouldn't happen since the sed command shouldn't accept it echo "Error: Invalid scale $scale in $command" exit 3 esac fi eval duration_ms=\$dur$duration if [ "$dotted" == "yes" ]; then extra=`expr $duration_ms / 2` duration_ms=`expr $extra + $duration_ms` fi if [ "$2" != "" ]; then echo " Note: $note" echo " Scale: $scale" echo " Frequency=$frequency Hz" echo " Duration: $duration" echo " Dotted: $dotted" echo " Duration: $duration_ms ms" echo fi commandline="${commandline} -n -f ${frequency} -l ${duration_ms}" done echo "done" if [ "$2" != "" ]; then echo "Command to run:" echo $commandline fi echo -n "Playing.. " eval $commandline echo "done" # Write cache if dir exists if [ -d ${HOME}/.rtttlplayer -a "$md5sum" != "" ]; then echo "#!/bin/sh" > ${HOME}/.rtttlplayer/$md5sum echo "echo \"-- ${name} --\"" >> ${HOME}/.rtttlplayer/$md5sum echo "echo -n \"Playing.. \"" >> ${HOME}/.rtttlplayer/$md5sum echo $commandline >> ${HOME}/.rtttlplayer/$md5sum echo "echo \"done\"" >> ${HOME}/.rtttlplayer/$md5sum fi