From da071d0d7c0067be55b2a6177ef01ee1ec5e88b8 Mon Sep 17 00:00:00 2001 From: Jonas Häggqvist Date: Fri, 3 Nov 2006 21:47:52 +0000 Subject: Voice update, most useful for non-Windows systems. Voice file building using Festival, Flite or eSpeak from the configure script. Compressing with Speex and Vorbis possible, but won't create useable voicefiles. Adds voicefont.c and wavtrim.c by Jörg Hohensohn. Also includes a script to generate talk clips. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11426 a1c6a512-1295-4272-9138-f99709370657 --- tools/Makefile | 6 ++ tools/configure | 172 +++++++++++++++++++++++++++++- tools/gentalkclips.sh | 152 +++++++++++++++++++++++++++ tools/genvoice.sh | 121 ++++++++++++++++++++++ tools/voicecommon.sh | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++ tools/voicefont.c | 218 ++++++++++++++++++++++++++++++++++++++ tools/wavtrim.c | 235 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1185 insertions(+), 1 deletion(-) create mode 100755 tools/gentalkclips.sh create mode 100755 tools/genvoice.sh create mode 100644 tools/voicecommon.sh create mode 100644 tools/voicefont.c create mode 100644 tools/wavtrim.c 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 < ) \___| < | \_\ ( <_> > < < +# 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 +#include + +/* 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 \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 ) \___| < | \_\ ( <_> > < < + * 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 /* for file I/O */ +#include /* 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 []\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 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 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) +*/ -- cgit v1.2.3