diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/Makefile | 6 | ||||
-rwxr-xr-x | tools/configure | 172 | ||||
-rwxr-xr-x | tools/gentalkclips.sh | 152 | ||||
-rwxr-xr-x | tools/genvoice.sh | 121 | ||||
-rw-r--r-- | tools/voicecommon.sh | 282 | ||||
-rw-r--r-- | tools/voicefont.c | 218 | ||||
-rw-r--r-- | tools/wavtrim.c | 235 |
7 files changed, 1185 insertions, 1 deletions
diff --git a/tools/Makefile b/tools/Makefile index 26cdcab230..1f8be87370 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -50,6 +50,12 @@ generate_rocklatin: generate_rocklatin.c ../firmware/drivers/lcd-player-charset. uclpack: $(SILENT)$(MAKE) -C ucl +wavtrim: wavtrim.c + $(SILENT)$(CC) -g $+ -o $@ + +voicefont: voicefont.c + $(SILENT)$(CC) -g $+ -o $@ + clean: @echo "Cleaning tools" $(SILENT)rm -f $(CLEANALL) $(shell for f in $(CLEANALL) ; do echo $$f.exe $$f.o $$f.obj ; done) *.ajf *~ diff --git a/tools/configure b/tools/configure index e7562f824b..60dc49cbc8 100755 --- a/tools/configure +++ b/tools/configure @@ -366,6 +366,154 @@ if [ -z "$simver" ]; then fi } +voiceconfig () { + echo "Building voice for $archos" + echo "" + + if [ `which flite` ]; then + FLITE="F(l)ite " + FLITE_OPTS="FLITE_OPTS=\"\"" + DEFAULT_TTS="flite" + DEFAULT_TTS_OPTS=$FLITE_OPTS + DEFAULT_NOISEFLOOR="500" + DEFAULT_CHOICE="L" + fi + if [ `which speak` ]; then + ESPEAK="(e)Speak " + ESPEAK_OPTS="ESPEAK_OPTS=\"\"" + DEFAULT_TTS="espeak" + DEFAULT_TTS_OPTS=$ESPEAK_OPTS + DEFAULT_NOISEFLOOR="500" + DEFAULT_CHOICE="e" + fi + if [ `which festival` ]; then + FESTIVAL="(F)estival " + FESTIVAL_OPTS="FLITE_OPTS=\"\"" + DEFAULT_TTS="festival" + DEFAULT_TTS_OPTS=$FESTIVAL_OPTS + DEFAULT_NOISEFLOOR="500" + DEFAULT_CHOICE="F" + fi + + if [ "$FESTIVAL" == "$FLITE" ] && [ "$FLITE" == "$ESPEAK" ]; then + echo "You need Festival, eSpeak or Flite in your path to build voice files" + exit + fi + + echo "TTS engine to use: ${FLITE}${FESTIVAL}${ESPEAK}(${DEFAULT_CHOICE})?" + option=`input` + case "$option" in + [Ll]) + TTS_ENGINE="flite" + NOISEFLOOR="500" # TODO: check this value + TTS_OPTS=$FLITE_OPTS + ;; + [Ee]) + TTS_ENGINE="espeak" + NOISEFLOOR="500" + TTS_OPTS=$ESPEAK_OPTS + ;; + [Ff]) + TTS_ENGINE="festival" + NOISEFLOOR="500" + TTS_OPTS=$FESTIVAL_OPTS + ;; + *) + TTS_ENGINE=$DEFAULT_TTS + TTS_OPTS=$DEFAULT_TTS_OPTS + NOISEFLOOR=$DEFAULT_NOISEFLOOR + esac + echo "Using $TTS_ENGINE for TTS" + + echo "" + + if [ `which oggenc` ]; then + OGGENC="(O)ggenc " + DEFAULT_ENC="oggenc" + VORBIS_OPTS="VORBIS_OPTS=\"-q0 --downmix\"" + DEFAULT_ENC_OPTS=$VORBIS_OPTS + DEFAULT_CHOICE="O" + fi + if [ `which speexenc` ]; then + SPEEXENC="(S)peexenc " + DEFAULT_ENC="speexenc" + SPEEX_OPTS="" # TODO: find appropriate options for speex + DEFAULT_ENC_OPTS=$SPEEX_OPTS + DEFAULT_CHOICE="S" + fi + if [ `which lame` ]; then + LAME="(L)ame " + DEFAULT_ENC="lame" + LAME_OPTS="LAME_OPTS=\"--resample 12 -t -m m -h -V 9 -S\"" + DEFAULT_ENC_OPTS=$LAME_OPTS + DEFAULT_CHOICE="L" + fi + + if [ "$LAME" == "" ]; then + echo "You need to have Lame installed to build voice files" + fi + + echo "Encoder to use: ${LAME}${OGGENC}${SPEEXENC}(${DEFAULT_CHOICE})?" + echo "" + echo "Note: Use Lame - the other options won't work" + option=`input` + case "$option" in + [Oo]) + ENCODER="oggenc" + ENC_OPTS=$VORBIS_OPTS + ;; + [Ss]) + ENCODER="speexenc" + ENC_OPTS=$SPEEX_OPTS + ;; + [Ll]) + ENCODER="lame" + ENC_OPTS=$LAME_OPTS + ;; + *) + ENCODER=$DEFAULT_ENC + ENC_OPTS=$DEFAULT_ENC_OPTS + esac + echo "Using $ENCODER for encoding voice clips" + + cat > voicesettings.sh <<EOF +TTS_ENGINE="${TTS_ENGINE}" +ENCODER="${ENCODER}" +TEMPDIR="${pwd}" +NOISEFLOOR="${NOISEFLOOR}" +${TTS_OPTS} +${ENC_OPTS} +EOF +} + +picklang() { + # figure out which languages that are around + for file in $rootdir/apps/lang/*.lang; do + clean=`echo $file | sed -e 's:.*/::g' | cut "-d." -f1` + langs="$langs $clean" + done + + num=1 + for one in $langs; do + echo "$num. $one" + num=`expr $num + 1` + done + + read pick + return $pick; +} + +whichlang() { + num=1 + for one in $langs; do + if [ "$num" = "$pick" ]; then + echo $one + return + fi + num=`expr $num + 1` + done +} + target=$1 if test "$target" = "--help"; then @@ -1144,7 +1292,7 @@ fi esac echo "" - echo "Build (N)ormal, (D)evel, (S)imulator, (B)ootloader, $gdbstub(M)anual? (N)" + echo "Build (N)ormal, (D)evel, (S)imulator, (B)ootloader, $gdbstub(M)anual, (V)oice? (N)" option=`input`; @@ -1202,6 +1350,11 @@ fi apps="manual" echo "Manual build selected" ;; + [Vv]) + echo "Voice build selected" + voiceconfig + voice="yes" + ;; *) debug="" echo "Normal build selected" @@ -1238,6 +1391,20 @@ echo "Using source code root directory: $rootdir" # this was once possible to change at build-time, but no more: language="english" +# Ask about language if building voice +if [ "yes" == "$voice" ]; then + echo "Select a number for the language to use (default is english)" + + picklang + language=`whichlang` + + if [ -z "$language" ]; then + # pick a default + language="english" + fi + echo "Language set to $language" +fi + uname=`uname` if [ "yes" = "$simulator" ]; then @@ -1516,6 +1683,9 @@ clean: @ARCHOSROM@ @FLASHFILE@ UI256.bmp rockbox-full.zip \ html txt rockbox-manual*.zip +voice: tools + \$(TOOLSDIR)/genvoice.sh \$(ROOTDIR) \$(LANGUAGE) \$(ARCHOS) voicesettings.sh + tools: \$(SILENT)\$(MAKE) -C \$(TOOLSDIR) CC=\$(HOSTCC) @TOOLSET@ diff --git a/tools/gentalkclips.sh b/tools/gentalkclips.sh new file mode 100755 index 0000000000..8da3b37704 --- /dev/null +++ b/tools/gentalkclips.sh @@ -0,0 +1,152 @@ +#!/bin/sh +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# +# Copyright (c) 2004 Daniel Gudlat +# - http://www.rockbox.org/tracker/task/2131 +# Copyright (c) 2006 Jonas Häggqvist +# - This version, only dirwalk and the following comments remains +# +# All files in this archive are subject to the GNU General Public License. +# See the file COPYING in the source tree root for full license agreement. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# Note: You may wish to change some of the settings below. +# +# A script to automatically generate audio clips containing the names of all +# folders in a directory tree for use with the "Talkbox" feature available to +# users of the Rockbox open source firmware for Archos MP3 players and +# recorders as well as several other devices. Talkbox permits the device to +# speak the names of the folders as one navigates the directory structure on +# the device, thus permitting "eyes-free" use for those for whom the usual +# visual navigation is difficult or simply inadvisable. +# +# Audio clips are captured and stored in wave format, then converted into MP3 +# format by a third party application (lame). If you execute the script, +# passing it the top level of your music file hierarchy as an argument while +# your device is connected to your PC, then the resulting audio clips will be +# generated and stored in the correct location for use with the Talkbox +# feature. Alternatively, if you mirror your music folder structure from your +# PC to your Archos device, you can just run the script on the PC and then +# update the files on your Archos with your usual synchronization routine. +# +# NOTE: If you don't already have them installed, you may obtain the Festival +# text to speech system and several voices at: +# +# http://www.cstr.ed.ac.uk/projects/festival.html +# http://festvox.org/festival/ +# +# The most pleasant freely available Festival voice I know of is the slt_arctic +# voice from HST at http://hts.ics.nitech.ac.jp/ +# +# Known bugs +# - This script generates talk clips for all files, Rockbox only uses talk clips +# for music files it seems. + +# Include voicecommon.sh from the same dir as this script +# Any settings from voicecommon can be overridden if added below the following +# line. +source `dirname $0`'/voicecommon.sh' + +#################### +# General settings # +#################### + +# which TTS engine to use. Available: festival, flite, espeak +TTS_ENGINE=festival +# which encoder to use, available: lame, speex, vorbis (only lame will produce +# functional voice clips) +ENCODER=lame +# whether to overwrite existing mp3 files or only create missing ones (Y/N) +OVERWRITE_TALK=N +# whether, when overwriting mp3 files, also to regenerate all the wav files +OVERWRITE_WAV=N +# whether to remove the intermediary wav files after creating the mp3 files +REMOVE_WAV=Y +# whether to recurse into subdirectories +RECURSIVE=Y +# whether to strip extensions from filenames +STRIP_EXTENSIONS=Y + +################### +# End of settings # +################### + +strip_extension() { + TO_SPEAK=$1 + # XXX: add any that needs adding + for ext in mp3 ogg flac mpc sid; do + TO_SPEAK=`echo "$TO_SPEAK" |sed "s/\.$ext//i"` + done +} + +# Walk directory $1, creating talk files if necessary, descend into +# subdirecotries if specified +dirwalk() { + if [ -d "$1" ]; then + for i in "$1"/*; do + # Do not generate talk clip for talk(.wav) files + if [ `echo "$i" | grep -c "\.talk$"` -ne 0 ] || \ + [ `echo "$i" | grep -c "\.talk\.wav$"` -ne 0 ]; then + echo "Notice: Skipping file \"$i\"" + continue + fi + + TO_SPEAK=`basename "$i"` + if [ X$STRIP_EXTENSIONS = XY ]; then + strip_extension "$TO_SPEAK" + fi + + if [ -d "$i" ]; then + # $i is a dir: + SAVE_AS="$i"/_dirname.talk + WAV_FILE="$SAVE_AS".wav + + # If a talk clip already exists, only generate a new one if + # specified + if [ ! -f "$SAVE_AS" ] || [ X$OVERWRITE_TALK = XY ]; then + voice "$TO_SPEAK" "$WAV_FILE" + encode "$WAV_FILE" "$SAVE_AS" + fi + + # Need to be done lastly, or all variables will be dirty + if [ X$RECURSIVE = XY ]; then + dirwalk "$i" + fi + else + # $i is a file: + SAVE_AS="$i".talk + WAV_FILE="$SAVE_AS".wav + + # If a talk clip already exists, only generate a new one if + # specified + if [ ! -f "$i.talk" ] || [ X$OVERWRITE_TALK != XY ]; then + voice "$TO_SPEAK" "$WAV_FILE" + encode "$WAV_FILE" "$SAVE_AS" + fi + fi + # Remove wav file if specified + if [ X$REMOVEWAV = XY ]; then + rm -f "$WAV_FILE" + fi + done + else + echo "Warning: $1 is not a directory" + fi +} + +init_tts +init_encoder +if [ $# -gt 0 ]; then + dirwalk "$*" +else + dirwalk . +fi +stop_tts diff --git a/tools/genvoice.sh b/tools/genvoice.sh new file mode 100755 index 0000000000..5b667ea3ba --- /dev/null +++ b/tools/genvoice.sh @@ -0,0 +1,121 @@ +#!/bin/sh +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# +# Copyright 2006 Jonas Häggqvist, some parts Copyright 2004 Daniel Gudlat +# +# All files in this archive are subject to the GNU General Public License. +# See the file COPYING in the source tree root for full license agreement. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. + +# Include voicecommon.sh from the same dir as this script +# Any settings from voicecommon can be overridden if added below the following +# line. +source `dirname $0`'/voicecommon.sh' + +#################### +# General settings # +#################### + +# These settings can be overridden by passing a file with definitions as +# the fourth parameter to this script + +# which TTS engine to use. Available: festival, flite, espeak +TTS_ENGINE=festival +# which encoder to use, available: lame, speex, vorbis (only lame will produce +# functional voice clips at this point) +ENCODER=lame +# Where to save temporary files +TEMPDIR=/tmp + +################### +# End of settings # +################### + +createvoicefile() { + $VOICEFONT "$LANG_FILE" "$TEMPDIR/" "./$RLANG.voice" +} + +deletefiles() { + # XXX: might be unsafe depending on the value of TEMPDIR + rm -f "${TEMPDIR}"/LANG_* + rm -f "${TEMPDIR}"/VOICE_* +} + +generateclips() { + ROCKBOX_DIR="$1" + RLANG="$2" + TARGET="$3" + GENLANG="$ROCKBOX_DIR"/tools/genlang + ENGLISH="$ROCKBOX_DIR"/apps/lang/english.lang + LANG_FILE="$ROCKBOX_DIR"/apps/lang/$RLANG.lang + + $GENLANG -e=$ENGLISH -o -t=$TARGET $LANG_FILE |( + i=0 + while read line; do + case `expr $i % 3` in + 0) + # String ID no. + NUMBER=`echo $line |cut -b 2-` + ;; + 1) + # String ID + ID=`echo $line |cut -b 5-` + ;; + 2) + # String + STRING=`echo $line |cut -b 8-` + + # Now generate the file + voice "$STRING" "$TEMPDIR/$ID".wav + encode "$TEMPDIR/$ID".wav "$TEMPDIR/$ID".mp3 + ;; + esac + i=`expr $i + 1` + done + ) +} + +if [ -z "$3" ]; then + echo "Usage: $0 rockboxdirectory language target [settingsfile]"; + exit 32 +else + if [ ! -d "$1" ] || [ ! -f "$1/tools/genlang" ]; then + echo "Error: $1 is not a Rockbox directory" + exit 33 + fi + if [ ! -f "$1/apps/lang/$2.lang" ]; then + echo "Error: $2 is not a valid language" + exit 34 + fi + if [ ! -z "$4" ]; then + if [ -f "$4" ]; then + # Read settings from file + source "$4" + else + echo "Error: $4 does not exist" + exit 36 + fi + fi + # XXX: check for valid $TARGET? +fi + +VOICEFONT=`dirname $0`/voicefont +if [ ! -x $VOICEFONT ]; then + echo "Error: $VOICEFONT does not exist or is not executable" + exit 35 +fi + +init_tts +init_encoder +generateclips "$1" "$2" "$3" +stop_tts +createvoicefile +#deletefiles diff --git a/tools/voicecommon.sh b/tools/voicecommon.sh new file mode 100644 index 0000000000..d6759d834b --- /dev/null +++ b/tools/voicecommon.sh @@ -0,0 +1,282 @@ +#!/bin/sh +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# +# Copyright (c) 2006 Jonas Häggqvist +# +# All files in this archive are subject to the GNU General Public License. +# See the file COPYING in the source tree root for full license agreement. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# A selection of functions common to creating voicefiles for Rockbox. +# +# You may wish to change some of the settings below. + +##################### +# Program locations # +##################### + +# Leave any you're not using untouched, enter full path if the program is +# not found + +# the festival main executable +FESTIVAL_BIN=festival +# the festival_client binary +FESTIVAL_CLIENT=festival_client + +# The flite executable +FLITE_BIN=flite + +# The eSpeak executable +ESPEAK_BIN=speak + +# The lame executable +LAME_BIN=lame + +# The speexenc executable +SPEEX_BIN=speexenc + +# The oggenc executable +VORBIS_BIN=oggenc + +# The wavtrim executable +WAVTRIM=`dirname $0`/wavtrim + +##################### +# Festival settings # +##################### + +# If you're not using festival, leave untouched + +# whether to start the Festival server locally (Y/N) +FESTIVAL_START=Y +# the host of the Festival server +# this is set to localhost automatically when FESTIVAL_START is Y +FESTIVAL_HOST=localhost +# the port of the Festival server +FESTIVAL_PORT=1314 +# where to log the Festival client output +FESTIVAL_LOG=/dev/null +# other options to the festival client +FESTIVAL_OPTS="" + +################## +# Flite settings # +################## + +# If you're not using flite, leave untouched +FLITE_OPTS="" + +################### +# eSpeak settings # +################### + +# If you're not using eSpeak, leave untouched +ESPEAK_OPTS="" + +#################### +# Wavtrim settings # +#################### + +# The maximum sample value that will be treated as silence by the wavtrim tool. +# The value is expressed as an absolute 16 bit integer sample value (0 dB equals +# 32767). +# +# 500 is a good guess - at least for Festival + +NOISEFLOOR='500' + +##################### +# Encoding settings # +##################### +# where to log the encoder output +ENC_LOG=/dev/null + +# Suggested: --vbr-new -t --nores -S +# VBR, independent frames, silent mode +LAME_OPTS="--vbr-new -t --nores -S" + +# Suggested: +# XXX: suggest a default +SPEEX_OPTS="" + +# Suggested: -q0 --downmix +# Low quality, mono +VORBIS_OPTS="-q0 --downmix" + +################### +# End of settings # +################### + +# Check if executables exist and perform any necessary initialisation +init_tts() { + case $TTS_ENGINE in + festival) + # Check for festival_client + if [ ! `which $FESTIVAL_CLIENT` ]; then + echo "Error: $FESTIVAL_CLIENT not found" + exit 4 + fi + + # Check for, and start festival server if specified + if [ X$FESTIVAL_START = XY ]; then + if [ ! `which $FESTIVAL_BIN` ]; then + echo "Error: $FESTIVAL_BIN not found" + exit 3 + fi + FESTIVAL_HOST='localhost' + $FESTIVAL_BIN --server 2>&1 > /dev/null & + FESTIVAL_SERVER_PID=$! + sleep 3 + if [ `ps | grep -c "^\ *$FESTIVAL_SERVER_PID"` -ne 1 ]; then + echo "Error: Festival not started" + exit 9 + fi + fi + # Test connection to festival server + output=`echo -E "Rockbox" | $FESTIVAL_CLIENT --server \ + $FESTIVAL_HOST --otype riff --ttw --output \ + /dev/null 2>&1` + if [ $? -ne 0 ]; then + echo "Error: Couldn't connect to festival server at" \ + "$FESTIVAL_HOST ($output)" + exit 8 + fi + ;; + flite) + # Check for flite + if [ ! `which $FLITE_BIN` ]; then + echo "Error: $FLITE_BIN not found" + exit 5 + fi + ;; + espeak) + # Check for flite + if [ ! `which $ESPEAK_BIN` ]; then + echo "Error: $ESPEAK_BIN not found" + exit 5 + fi + ;; + *) + echo "Error: no valid TTS engine selected: $TTS_ENGINE" + exit 2 + ;; + esac + if [ ! -x $WAVTRIM ]; then + echo "Error: $WAVTRIM is not available" + exit 11 + fi +} + +# Perform any necessary shutdown for TTS engine +stop_tts() { + case $TTS_ENGINE in + festival) + if [ X$FESTIVAL_START = XY ]; then + # XXX: This is probably possible to do using festival_client + kill $FESTIVAL_SERVER_PID > /dev/null 2>&1 + fi + ;; + esac +} + +# Check if executables exist and perform any necessary initialisation +init_encoder() { + case $ENCODER in + lame) + # Check for lame binary + if [ ! `which $LAME_BIN` ]; then + echo "Error: $LAME_BIN not found" + exit 6 + fi + ;; + speex) + # Check for speexenc binary + if [ ! `which $SPEEX_BIN` ]; then + echo "Error: $SPEEX_BIN not found" + exit 7 + fi + ;; + vorbis) + # Check for vorbis encoder binary + if [ ! `which $VORBIS_BIN` ]; then + echo "Error: $VORBIS_BIN not found" + exit 10 + fi + ;; + *) + echo "Error: no valid encoder selected: $ENCODER" + exit 1 + ;; + esac + +} + +# Encode file $1 with ENCODER and save the result in $2, delete $1 if specified +encode() { + INPUT=$1 + OUTPUT=$2 + + if [ ! -f "$INPUT" ]; then + echo "Warning: missing input file: \"$INPUT\"" + else + echo "Action: Encode $OUTPUT with $ENCODER" + case $ENCODER in + lame) + $LAME_BIN $LAME_OPTS "$WAV_FILE" "$OUTPUT" >>$ENC_LOG 2>&1 + ;; + speex) + $SPEEX_BIN $SPEEX_OPTS "$WAV_FILE" "$OUTPUT" >>$ENC_LOG 2>&1 + ;; + vorbis) + $VORBIS_BIN $VORBIS_OPTS "$WAV_FILE" -o "$OUTPUT" >>$ENC_LOG 2>&1 + esac + if [ ! -f "$OUTPUT" ]; then + echo "Warning: missing output file \"$OUTPUT\"" + fi + fi +} + +# Generate file $2 containing $1 spoken by TTS_ENGINE, trim silence +voice() { + TO_SPEAK=$1 + WAV_FILE=$2 + if [ ! -f "$WAV_FILE" ] || [ X$OVERWRITE_WAV = XY ]; then + if [ "${TO_SPEAK}" == "" ]; then + touch "$WAV_FILE" + else + case $TTS_ENGINE in + festival) + echo "Action: Generate $WAV_FILE with festival" + echo -E "$TO_SPEAK" | $FESTIVAL_CLIENT $FESTIVAL_OPTS \ + --server $FESTIVAL_HOST \ + --otype riff --ttw --output "$WAV_FILE" 2>"$WAV_FILE" + ;; + espeak) + echo "Action: Generate $WAV_FILE with eSpeak" + echo $ESPEAK_BIN $ESPEAK_OPTS -w "$WAV_FILE" + echo -E "$TO_SPEAK" | $ESPEAK_BIN $ESPEAK_OPTS -w "$WAV_FILE" + ;; + flite) + echo "Action: Generate $WAV_FILE with flite" + echo -E "$TO_SPEAK" | $FLITE_BIN $FLITE_OPTS -o "$WAV_FILE" + ;; + esac + fi + fi + trim "$WAV_FILE" +} + +# Trim wavefile $1 +trim() { + WAVEFILE="$1" + echo "Action: Trim $WAV_FILE" + $WAVTRIM "$WAVEFILE" $NOISEFLOOR +} diff --git a/tools/voicefont.c b/tools/voicefont.c new file mode 100644 index 0000000000..0a6d0a2121 --- /dev/null +++ b/tools/voicefont.c @@ -0,0 +1,218 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2004 by Jörg Hohensohn + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * A tool to generate the Rockbox "voicefont", a collection of all the UI + * strings. + * + * Details at http://www.rockbox.org/twiki/bin/view/Main/VoiceBuilding + * + ****************************************************************************/ + +#include <stdio.h> +#include <string.h> + +/* endian conversion macros */ +#define SWAP2(x) ((((unsigned)(x)>>8) & 0x00ff) | (((unsigned)(x)<<8) & 0xff00)) +#define SWAP4(x) ((((unsigned)(x)>>24) & 0x000000ff) |\ + (((unsigned)(x)>>8) & 0x0000ff00) |\ + (((unsigned)(x)<<8) & 0x00ff0000) |\ + (((unsigned)(x)<<24) & 0xff000000)) + + +/* bitswap audio bytes, LSB becomes MSB and vice versa */ +int BitswapAudio (unsigned char* pDest, unsigned char* pSrc, size_t len) +{ + static const unsigned char Lookup[256] = + { + 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, + 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, + 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, + 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, + 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, + 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, + 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, + 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, + 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, + 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, + 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, + 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, + 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, + 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, + 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, + 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF, + }; + + while (len--) + *pDest++ = Lookup[*pSrc++]; + + return 0; +} + + +int main (int argc, char** argv) +{ + FILE* pFile; + + int i,j; + + /* two tables, one for normal strings, one for voice-only (>0x8000) */ + static char names[1000][80]; /* worst-case space */ + char name[80]; /* one string ID */ + static int pos[1000]; /* position of sample */ + static int size[1000]; /* length of clip */ + int voiceonly[1000]; /* flag if this is voice only */ + int count = 0; + int count_voiceonly = 0; + unsigned int value; /* value to be written to file */ + static unsigned char buffer[65535]; /* clip buffer, allow only 64K */ + int fields; + char line[255]; /* one line from the .lang file */ + char mp3filename1[1024]; + char mp3filename2[1024]; + char* mp3filename; + FILE* pMp3File; + + + if (argc < 2) + { + printf("Makes a Rockbox voicefont from a collection of mp3 clips.\n"); + printf("Usage: voicefont <language file> <mp3 path> <output file>\n"); + printf("\n"); + printf("Example: \n"); + printf("voicefont english.lang voice\\ voicefont.bin\n"); + return -1; + } + + pFile = fopen(argv[1], "r"); + if (pFile == NULL) + { + printf("Error opening language file %s\n", argv[1]); + return -2; + } + + memset(voiceonly, 0, sizeof(voiceonly)); + while (!feof(pFile)) + { + fgets(line, sizeof(line), pFile); + if (line[0] == '#') /* comment */ + continue; + + fields = sscanf(line, " id: %s", name); + if (fields == 1) + { + count++; /* next entry started */ + strcpy(names[count-1], name); + if (strncmp("VOICE_", name, 6) == 0) /* voice-only id? */ + voiceonly[count-1] = 1; + continue; + } + } + fclose(pFile); + + pFile = fopen(argv[3], "wb"); + if (pFile == NULL) + { + printf("Error opening output file %s\n", argv[3]); + return -2; + } + fseek(pFile, 16 + count*8, SEEK_SET); /* space for header */ + + for (i=0; i<count; i++) + { + if (voiceonly[i] == 1) + count_voiceonly++; + + pos[i] = ftell(pFile); + sprintf(mp3filename1, "%s%s.mp3", argv[2], names[i]); + sprintf(mp3filename2, "%s%s.wav.mp3", argv[2], names[i]); + mp3filename = mp3filename1; + pMp3File = fopen(mp3filename, "rb"); + if (pMp3File == NULL) + { /* alternatively, try the lame default filename */ + mp3filename = mp3filename2; + pMp3File = fopen(mp3filename, "rb"); + if (pMp3File == NULL) + { + printf("mp3 file %s not found!\n", mp3filename1); + size[i] = 0; + continue; + } + } + printf("processing %s\n", mp3filename); + + size[i] = fread(buffer, 1, sizeof(buffer), pMp3File); + fclose(pMp3File); + BitswapAudio(buffer, buffer, size[i]); + fwrite(buffer, 1, size[i], pFile); + + printf("%d %s %d\n", i, names[i], size[i]); /* debug */ + } /* for i */ + + + fseek(pFile, 0, SEEK_SET); + + /* Create the file format: */ + + /* 1st 32 bit value in the file is the version number */ + value = SWAP4(200); /* 2.00 */ + fwrite(&value, sizeof(value), 1, pFile); + + /* 2nd 32 bit value in the file is the header size (= 1st table position) */ + value = SWAP4(16); /* 16 bytes: for version, header size, number1, number2 */ + fwrite(&value, sizeof(value), 1, pFile); + + /* 3rd 32 bit value in the file is the number of clips in 1st table */ + value = SWAP4(count-count_voiceonly); + fwrite(&value, sizeof(value), 1, pFile); + + /* 4th bit value in the file is the number of clips in 2nd table */ + value = SWAP4(count_voiceonly); + fwrite(&value, sizeof(value), 1, pFile); + + /* then followed by offset/size pairs for each clip */ + for (j=0; j<2; j++) /* now 2 tables */ + { + for (i=0; i<count; i++) + { + if (j == 0) /* first run, skip the voice only ones */ + { + if (voiceonly[i] == 1) + continue; + } + else /* second run, skip the non voice only ones */ + { + if (!voiceonly[i] == 1) + continue; + } + + value = SWAP4(pos[i]); /* position */ + fwrite(&value, sizeof(value), 1, pFile); + value = SWAP4(size[i]); /* size */ + fwrite(&value, sizeof(value), 1, pFile); + } /* for i */ + } /* for j */ + + + /* + * after this the actual bitswapped mp3 data follows, + * which we already have written, see above. + */ + + fclose(pFile); + + return 0; +} diff --git a/tools/wavtrim.c b/tools/wavtrim.c new file mode 100644 index 0000000000..86434235ba --- /dev/null +++ b/tools/wavtrim.c @@ -0,0 +1,235 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2004 by Jörg Hohensohn + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * Details at http://www.rockbox.org/twiki/bin/view/Main/VoiceBuilding + * + ****************************************************************************/ + + +#include <stdio.h> /* for file I/O */ +#include <stdlib.h> /* for malloc */ + +/* place a 32 bit value into memory, little endian */ +void Write32(unsigned char* pByte, unsigned long value) +{ + pByte[0] = (unsigned char)value; + pByte[1] = (unsigned char)(value >> 8); + pByte[2] = (unsigned char)(value >> 16); + pByte[3] = (unsigned char)(value >> 24) ; +} + + +/* read a 32 bit value from memory, little endian */ +unsigned long Read32(unsigned char* pByte) +{ + unsigned long value = 0; + + value |= (unsigned long)pByte[0]; + value |= (unsigned long)pByte[1] << 8; + value |= (unsigned long)pByte[2] << 16; + value |= (unsigned long)pByte[3] << 24; + + return value; +} + + +/* place a 16 bit value into memory, little endian */ +void Write16(unsigned char* pByte, unsigned short value) +{ + pByte[0] = (unsigned char)value; + pByte[1] = (unsigned char)(value >> 8); +} + + +/* read a 16 bit value from memory, little endian */ +unsigned long Read16(unsigned char* pByte) +{ + unsigned short value = 0; + + value |= (unsigned short)pByte[0]; + value |= (unsigned short)pByte[1] << 8; + + return value; +} + + +int main (int argc, char** argv) +{ + FILE* pFile; + long lFileSize, lGot; + unsigned char* pBuf; + int bps; /* byte per sample */ + int sps; /* samples per second */ + int datapos; /* where the payload starts */ + int skip_head, skip_tail, pad_head, pad_tail; + int i; + int max_silence = 0; + signed char sample8; + short sample16; + + if (argc < 2) + { + printf("wavtrim removes silence at the begin and end of a WAV file.\n"); + printf("usage: wavtrim <filename.wav> [<max_silence>]\n"); + return 0; + } + + if (argc == 3) + { + max_silence = atoi(argv[2]); + } + + pFile = fopen(argv[1], "rb"); + if (pFile == NULL) + { + printf("Error opening file %s for reading\n", argv[1]); + return -1; + } + + fseek(pFile, 0, SEEK_END); + lFileSize = ftell(pFile); + fseek(pFile, 0, SEEK_SET); + + pBuf = malloc(lFileSize); + if (pBuf == NULL) + { + printf("Out of memory to allocate %ld bytes for file.\n", lFileSize); + fclose(pFile); + return -1; + } + + lGot = fread(pBuf, 1, lFileSize, pFile); + fclose(pFile); + if (lGot != lFileSize) + { + printf("File read error, got only %ld bytes out of %ld.\n", lGot, lFileSize); + free(pBuf); + return -1; + } + + bps = Read16(pBuf + 32); + datapos = 28 + Read16(pBuf + 16); + + if (Read32(pBuf) != 0x46464952 /* "RIFF" */ + || Read32(pBuf+8) != 0x45564157 /* "WAVE" */ + || Read32(pBuf+12) != 0x20746d66 /* "fmt " */ + || Read32(pBuf+datapos-8) != 0x61746164) /* "data" */ + { + printf("No valid input WAV file?\n", lGot, lFileSize); + free(pBuf); + return -1; + } + + sps = Read32(pBuf + 24); + pad_head = sps * 10 / 1000; /* 10 ms */ + pad_tail = sps * 10 / 1000; /* 10 ms */ + + if (bps == 1) /* 8 bit samples */ + { + + max_silence >>= 8; + + /* clip the start */ + for (i=datapos; i<lFileSize; i++) + { + sample8 = pBuf[i] - 0x80; + if (abs(sample8) > max_silence) + break; + } + skip_head = i - datapos; + skip_head = (skip_head > pad_head) ? skip_head - pad_head : 0; + + /* clip the end */ + for (i=lFileSize-1; i>datapos+skip_head; i--) + { + sample8 = pBuf[i] - 0x80; + if (abs(sample8) > max_silence) + break; + } + skip_tail = lFileSize - 1 - i; + skip_tail = (skip_tail > pad_tail) ? skip_tail - pad_tail : 0; + } + else if (bps == 2) /* 16 bit samples */ + { + + /* clip the start */ + for (i=datapos; i<lFileSize; i+=2) + { + sample16 = *(short *)(pBuf + i); + if (abs(sample16) > max_silence) + break; + } + skip_head = i - datapos; + skip_head = (skip_head > 2 * pad_head) ? + skip_head - 2 * pad_head : 0; + + /* clip the end */ + for (i=lFileSize-2; i>datapos+skip_head; i-=2) + { + sample16 = *(short *)(pBuf + i); + if (abs(sample16) > max_silence) + break; + } + skip_tail = lFileSize - 2 - i; + skip_tail = (skip_tail > 2 * pad_tail) ? + skip_tail - 2 * pad_tail : 0; + } + + /* update the size in the headers */ + Write32(pBuf+4, Read32(pBuf+4) - skip_head - skip_tail); + Write32(pBuf+datapos-4, Read32(pBuf+datapos-4) - skip_head - skip_tail); + + pFile = fopen(argv[1], "wb"); + if (pFile == NULL) + { + printf("Error opening file %s for writing\n", argv[1]); + return -1; + } + + /* write the new file */ + fwrite(pBuf, 1, datapos, pFile); /* write header */ + fwrite(pBuf + datapos + skip_head, 1, lFileSize - datapos - skip_head - skip_tail, pFile); + fclose(pFile); + + free(pBuf); + return 0; +} + +/* +RIFF Chunk (12 bytes in length total) +0 - 3 "RIFF" (ASCII Characters) +4 - 7 Total Length Of Package To Follow (Binary, little endian) +8 - 11 "WAVE" (ASCII Characters) + + +FORMAT Chunk (24 or 26 bytes in length total) Byte Number +12 - 15 "fmt_" (ASCII Characters) +16 - 19 Length Of FORMAT Chunk (Binary, 0x10 or 0x12 seen) +20 - 21 Always 0x01 +22 - 23 Channel Numbers (Always 0x01=Mono, 0x02=Stereo) +24 - 27 Sample Rate (Binary, in Hz) +28 - 31 Bytes Per Second +32 - 33 Bytes Per Sample: 1=8 bit Mono, 2=8 bit Stereo or 16 bit Mono, 4=16 bit Stereo +34 - 35 Bits Per Sample + + +DATA Chunk Byte Number +36 - 39 "data" (ASCII Characters) +40 - 43 Length Of Data To Follow +44 - end + Data (Samples) +*/ |