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. | |||
50 | uclpack: | 50 | uclpack: |
51 | $(SILENT)$(MAKE) -C ucl | 51 | $(SILENT)$(MAKE) -C ucl |
52 | 52 | ||
53 | wavtrim: wavtrim.c | ||
54 | $(SILENT)$(CC) -g $+ -o $@ | ||
55 | |||
56 | voicefont: voicefont.c | ||
57 | $(SILENT)$(CC) -g $+ -o $@ | ||
58 | |||
53 | clean: | 59 | clean: |
54 | @echo "Cleaning tools" | 60 | @echo "Cleaning tools" |
55 | $(SILENT)rm -f $(CLEANALL) $(shell for f in $(CLEANALL) ; do echo $$f.exe $$f.o $$f.obj ; done) *.ajf *~ | 61 | $(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 | |||
366 | fi | 366 | fi |
367 | } | 367 | } |
368 | 368 | ||
369 | voiceconfig () { | ||
370 | echo "Building voice for $archos" | ||
371 | echo "" | ||
372 | |||
373 | if [ `which flite` ]; then | ||
374 | FLITE="F(l)ite " | ||
375 | FLITE_OPTS="FLITE_OPTS=\"\"" | ||
376 | DEFAULT_TTS="flite" | ||
377 | DEFAULT_TTS_OPTS=$FLITE_OPTS | ||
378 | DEFAULT_NOISEFLOOR="500" | ||
379 | DEFAULT_CHOICE="L" | ||
380 | fi | ||
381 | if [ `which speak` ]; then | ||
382 | ESPEAK="(e)Speak " | ||
383 | ESPEAK_OPTS="ESPEAK_OPTS=\"\"" | ||
384 | DEFAULT_TTS="espeak" | ||
385 | DEFAULT_TTS_OPTS=$ESPEAK_OPTS | ||
386 | DEFAULT_NOISEFLOOR="500" | ||
387 | DEFAULT_CHOICE="e" | ||
388 | fi | ||
389 | if [ `which festival` ]; then | ||
390 | FESTIVAL="(F)estival " | ||
391 | FESTIVAL_OPTS="FLITE_OPTS=\"\"" | ||
392 | DEFAULT_TTS="festival" | ||
393 | DEFAULT_TTS_OPTS=$FESTIVAL_OPTS | ||
394 | DEFAULT_NOISEFLOOR="500" | ||
395 | DEFAULT_CHOICE="F" | ||
396 | fi | ||
397 | |||
398 | if [ "$FESTIVAL" == "$FLITE" ] && [ "$FLITE" == "$ESPEAK" ]; then | ||
399 | echo "You need Festival, eSpeak or Flite in your path to build voice files" | ||
400 | exit | ||
401 | fi | ||
402 | |||
403 | echo "TTS engine to use: ${FLITE}${FESTIVAL}${ESPEAK}(${DEFAULT_CHOICE})?" | ||
404 | option=`input` | ||
405 | case "$option" in | ||
406 | [Ll]) | ||
407 | TTS_ENGINE="flite" | ||
408 | NOISEFLOOR="500" # TODO: check this value | ||
409 | TTS_OPTS=$FLITE_OPTS | ||
410 | ;; | ||
411 | [Ee]) | ||
412 | TTS_ENGINE="espeak" | ||
413 | NOISEFLOOR="500" | ||
414 | TTS_OPTS=$ESPEAK_OPTS | ||
415 | ;; | ||
416 | [Ff]) | ||
417 | TTS_ENGINE="festival" | ||
418 | NOISEFLOOR="500" | ||
419 | TTS_OPTS=$FESTIVAL_OPTS | ||
420 | ;; | ||
421 | *) | ||
422 | TTS_ENGINE=$DEFAULT_TTS | ||
423 | TTS_OPTS=$DEFAULT_TTS_OPTS | ||
424 | NOISEFLOOR=$DEFAULT_NOISEFLOOR | ||
425 | esac | ||
426 | echo "Using $TTS_ENGINE for TTS" | ||
427 | |||
428 | echo "" | ||
429 | |||
430 | if [ `which oggenc` ]; then | ||
431 | OGGENC="(O)ggenc " | ||
432 | DEFAULT_ENC="oggenc" | ||
433 | VORBIS_OPTS="VORBIS_OPTS=\"-q0 --downmix\"" | ||
434 | DEFAULT_ENC_OPTS=$VORBIS_OPTS | ||
435 | DEFAULT_CHOICE="O" | ||
436 | fi | ||
437 | if [ `which speexenc` ]; then | ||
438 | SPEEXENC="(S)peexenc " | ||
439 | DEFAULT_ENC="speexenc" | ||
440 | SPEEX_OPTS="" # TODO: find appropriate options for speex | ||
441 | DEFAULT_ENC_OPTS=$SPEEX_OPTS | ||
442 | DEFAULT_CHOICE="S" | ||
443 | fi | ||
444 | if [ `which lame` ]; then | ||
445 | LAME="(L)ame " | ||
446 | DEFAULT_ENC="lame" | ||
447 | LAME_OPTS="LAME_OPTS=\"--resample 12 -t -m m -h -V 9 -S\"" | ||
448 | DEFAULT_ENC_OPTS=$LAME_OPTS | ||
449 | DEFAULT_CHOICE="L" | ||
450 | fi | ||
451 | |||
452 | if [ "$LAME" == "" ]; then | ||
453 | echo "You need to have Lame installed to build voice files" | ||
454 | fi | ||
455 | |||
456 | echo "Encoder to use: ${LAME}${OGGENC}${SPEEXENC}(${DEFAULT_CHOICE})?" | ||
457 | echo "" | ||
458 | echo "Note: Use Lame - the other options won't work" | ||
459 | option=`input` | ||
460 | case "$option" in | ||
461 | [Oo]) | ||
462 | ENCODER="oggenc" | ||
463 | ENC_OPTS=$VORBIS_OPTS | ||
464 | ;; | ||
465 | [Ss]) | ||
466 | ENCODER="speexenc" | ||
467 | ENC_OPTS=$SPEEX_OPTS | ||
468 | ;; | ||
469 | [Ll]) | ||
470 | ENCODER="lame" | ||
471 | ENC_OPTS=$LAME_OPTS | ||
472 | ;; | ||
473 | *) | ||
474 | ENCODER=$DEFAULT_ENC | ||
475 | ENC_OPTS=$DEFAULT_ENC_OPTS | ||
476 | esac | ||
477 | echo "Using $ENCODER for encoding voice clips" | ||
478 | |||
479 | cat > voicesettings.sh <<EOF | ||
480 | TTS_ENGINE="${TTS_ENGINE}" | ||
481 | ENCODER="${ENCODER}" | ||
482 | TEMPDIR="${pwd}" | ||
483 | NOISEFLOOR="${NOISEFLOOR}" | ||
484 | ${TTS_OPTS} | ||
485 | ${ENC_OPTS} | ||
486 | EOF | ||
487 | } | ||
488 | |||
489 | picklang() { | ||
490 | # figure out which languages that are around | ||
491 | for file in $rootdir/apps/lang/*.lang; do | ||
492 | clean=`echo $file | sed -e 's:.*/::g' | cut "-d." -f1` | ||
493 | langs="$langs $clean" | ||
494 | done | ||
495 | |||
496 | num=1 | ||
497 | for one in $langs; do | ||
498 | echo "$num. $one" | ||
499 | num=`expr $num + 1` | ||
500 | done | ||
501 | |||
502 | read pick | ||
503 | return $pick; | ||
504 | } | ||
505 | |||
506 | whichlang() { | ||
507 | num=1 | ||
508 | for one in $langs; do | ||
509 | if [ "$num" = "$pick" ]; then | ||
510 | echo $one | ||
511 | return | ||
512 | fi | ||
513 | num=`expr $num + 1` | ||
514 | done | ||
515 | } | ||
516 | |||
369 | target=$1 | 517 | target=$1 |
370 | 518 | ||
371 | if test "$target" = "--help"; then | 519 | if test "$target" = "--help"; then |
@@ -1144,7 +1292,7 @@ fi | |||
1144 | esac | 1292 | esac |
1145 | 1293 | ||
1146 | echo "" | 1294 | echo "" |
1147 | echo "Build (N)ormal, (D)evel, (S)imulator, (B)ootloader, $gdbstub(M)anual? (N)" | 1295 | echo "Build (N)ormal, (D)evel, (S)imulator, (B)ootloader, $gdbstub(M)anual, (V)oice? (N)" |
1148 | 1296 | ||
1149 | option=`input`; | 1297 | option=`input`; |
1150 | 1298 | ||
@@ -1202,6 +1350,11 @@ fi | |||
1202 | apps="manual" | 1350 | apps="manual" |
1203 | echo "Manual build selected" | 1351 | echo "Manual build selected" |
1204 | ;; | 1352 | ;; |
1353 | [Vv]) | ||
1354 | echo "Voice build selected" | ||
1355 | voiceconfig | ||
1356 | voice="yes" | ||
1357 | ;; | ||
1205 | *) | 1358 | *) |
1206 | debug="" | 1359 | debug="" |
1207 | echo "Normal build selected" | 1360 | echo "Normal build selected" |
@@ -1238,6 +1391,20 @@ echo "Using source code root directory: $rootdir" | |||
1238 | # this was once possible to change at build-time, but no more: | 1391 | # this was once possible to change at build-time, but no more: |
1239 | language="english" | 1392 | language="english" |
1240 | 1393 | ||
1394 | # Ask about language if building voice | ||
1395 | if [ "yes" == "$voice" ]; then | ||
1396 | echo "Select a number for the language to use (default is english)" | ||
1397 | |||
1398 | picklang | ||
1399 | language=`whichlang` | ||
1400 | |||
1401 | if [ -z "$language" ]; then | ||
1402 | # pick a default | ||
1403 | language="english" | ||
1404 | fi | ||
1405 | echo "Language set to $language" | ||
1406 | fi | ||
1407 | |||
1241 | uname=`uname` | 1408 | uname=`uname` |
1242 | 1409 | ||
1243 | if [ "yes" = "$simulator" ]; then | 1410 | if [ "yes" = "$simulator" ]; then |
@@ -1516,6 +1683,9 @@ clean: | |||
1516 | @ARCHOSROM@ @FLASHFILE@ UI256.bmp rockbox-full.zip \ | 1683 | @ARCHOSROM@ @FLASHFILE@ UI256.bmp rockbox-full.zip \ |
1517 | html txt rockbox-manual*.zip | 1684 | html txt rockbox-manual*.zip |
1518 | 1685 | ||
1686 | voice: tools | ||
1687 | \$(TOOLSDIR)/genvoice.sh \$(ROOTDIR) \$(LANGUAGE) \$(ARCHOS) voicesettings.sh | ||
1688 | |||
1519 | tools: | 1689 | tools: |
1520 | \$(SILENT)\$(MAKE) -C \$(TOOLSDIR) CC=\$(HOSTCC) @TOOLSET@ | 1690 | \$(SILENT)\$(MAKE) -C \$(TOOLSDIR) CC=\$(HOSTCC) @TOOLSET@ |
1521 | 1691 | ||
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 @@ | |||
1 | #!/bin/sh | ||
2 | # __________ __ ___. | ||
3 | # Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | # \/ \/ \/ \/ \/ | ||
8 | # $Id$ | ||
9 | # | ||
10 | # Copyright (c) 2004 Daniel Gudlat | ||
11 | # - http://www.rockbox.org/tracker/task/2131 | ||
12 | # Copyright (c) 2006 Jonas Häggqvist | ||
13 | # - This version, only dirwalk and the following comments remains | ||
14 | # | ||
15 | # All files in this archive are subject to the GNU General Public License. | ||
16 | # See the file COPYING in the source tree root for full license agreement. | ||
17 | # | ||
18 | # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | # KIND, either express or implied. | ||
20 | # | ||
21 | # Note: You may wish to change some of the settings below. | ||
22 | # | ||
23 | # A script to automatically generate audio clips containing the names of all | ||
24 | # folders in a directory tree for use with the "Talkbox" feature available to | ||
25 | # users of the Rockbox open source firmware for Archos MP3 players and | ||
26 | # recorders as well as several other devices. Talkbox permits the device to | ||
27 | # speak the names of the folders as one navigates the directory structure on | ||
28 | # the device, thus permitting "eyes-free" use for those for whom the usual | ||
29 | # visual navigation is difficult or simply inadvisable. | ||
30 | # | ||
31 | # Audio clips are captured and stored in wave format, then converted into MP3 | ||
32 | # format by a third party application (lame). If you execute the script, | ||
33 | # passing it the top level of your music file hierarchy as an argument while | ||
34 | # your device is connected to your PC, then the resulting audio clips will be | ||
35 | # generated and stored in the correct location for use with the Talkbox | ||
36 | # feature. Alternatively, if you mirror your music folder structure from your | ||
37 | # PC to your Archos device, you can just run the script on the PC and then | ||
38 | # update the files on your Archos with your usual synchronization routine. | ||
39 | # | ||
40 | # NOTE: If you don't already have them installed, you may obtain the Festival | ||
41 | # text to speech system and several voices at: | ||
42 | # | ||
43 | # http://www.cstr.ed.ac.uk/projects/festival.html | ||
44 | # http://festvox.org/festival/ | ||
45 | # | ||
46 | # The most pleasant freely available Festival voice I know of is the slt_arctic | ||
47 | # voice from HST at http://hts.ics.nitech.ac.jp/ | ||
48 | # | ||
49 | # Known bugs | ||
50 | # - This script generates talk clips for all files, Rockbox only uses talk clips | ||
51 | # for music files it seems. | ||
52 | |||
53 | # Include voicecommon.sh from the same dir as this script | ||
54 | # Any settings from voicecommon can be overridden if added below the following | ||
55 | # line. | ||
56 | source `dirname $0`'/voicecommon.sh' | ||
57 | |||
58 | #################### | ||
59 | # General settings # | ||
60 | #################### | ||
61 | |||
62 | # which TTS engine to use. Available: festival, flite, espeak | ||
63 | TTS_ENGINE=festival | ||
64 | # which encoder to use, available: lame, speex, vorbis (only lame will produce | ||
65 | # functional voice clips) | ||
66 | ENCODER=lame | ||
67 | # whether to overwrite existing mp3 files or only create missing ones (Y/N) | ||
68 | OVERWRITE_TALK=N | ||
69 | # whether, when overwriting mp3 files, also to regenerate all the wav files | ||
70 | OVERWRITE_WAV=N | ||
71 | # whether to remove the intermediary wav files after creating the mp3 files | ||
72 | REMOVE_WAV=Y | ||
73 | # whether to recurse into subdirectories | ||
74 | RECURSIVE=Y | ||
75 | # whether to strip extensions from filenames | ||
76 | STRIP_EXTENSIONS=Y | ||
77 | |||
78 | ################### | ||
79 | # End of settings # | ||
80 | ################### | ||
81 | |||
82 | strip_extension() { | ||
83 | TO_SPEAK=$1 | ||
84 | # XXX: add any that needs adding | ||
85 | for ext in mp3 ogg flac mpc sid; do | ||
86 | TO_SPEAK=`echo "$TO_SPEAK" |sed "s/\.$ext//i"` | ||
87 | done | ||
88 | } | ||
89 | |||
90 | # Walk directory $1, creating talk files if necessary, descend into | ||
91 | # subdirecotries if specified | ||
92 | dirwalk() { | ||
93 | if [ -d "$1" ]; then | ||
94 | for i in "$1"/*; do | ||
95 | # Do not generate talk clip for talk(.wav) files | ||
96 | if [ `echo "$i" | grep -c "\.talk$"` -ne 0 ] || \ | ||
97 | [ `echo "$i" | grep -c "\.talk\.wav$"` -ne 0 ]; then | ||
98 | echo "Notice: Skipping file \"$i\"" | ||
99 | continue | ||
100 | fi | ||
101 | |||
102 | TO_SPEAK=`basename "$i"` | ||
103 | if [ X$STRIP_EXTENSIONS = XY ]; then | ||
104 | strip_extension "$TO_SPEAK" | ||
105 | fi | ||
106 | |||
107 | if [ -d "$i" ]; then | ||
108 | # $i is a dir: | ||
109 | SAVE_AS="$i"/_dirname.talk | ||
110 | WAV_FILE="$SAVE_AS".wav | ||
111 | |||
112 | # If a talk clip already exists, only generate a new one if | ||
113 | # specified | ||
114 | if [ ! -f "$SAVE_AS" ] || [ X$OVERWRITE_TALK = XY ]; then | ||
115 | voice "$TO_SPEAK" "$WAV_FILE" | ||
116 | encode "$WAV_FILE" "$SAVE_AS" | ||
117 | fi | ||
118 | |||
119 | # Need to be done lastly, or all variables will be dirty | ||
120 | if [ X$RECURSIVE = XY ]; then | ||
121 | dirwalk "$i" | ||
122 | fi | ||
123 | else | ||
124 | # $i is a file: | ||
125 | SAVE_AS="$i".talk | ||
126 | WAV_FILE="$SAVE_AS".wav | ||
127 | |||
128 | # If a talk clip already exists, only generate a new one if | ||
129 | # specified | ||
130 | if [ ! -f "$i.talk" ] || [ X$OVERWRITE_TALK != XY ]; then | ||
131 | voice "$TO_SPEAK" "$WAV_FILE" | ||
132 | encode "$WAV_FILE" "$SAVE_AS" | ||
133 | fi | ||
134 | fi | ||
135 | # Remove wav file if specified | ||
136 | if [ X$REMOVEWAV = XY ]; then | ||
137 | rm -f "$WAV_FILE" | ||
138 | fi | ||
139 | done | ||
140 | else | ||
141 | echo "Warning: $1 is not a directory" | ||
142 | fi | ||
143 | } | ||
144 | |||
145 | init_tts | ||
146 | init_encoder | ||
147 | if [ $# -gt 0 ]; then | ||
148 | dirwalk "$*" | ||
149 | else | ||
150 | dirwalk . | ||
151 | fi | ||
152 | 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 @@ | |||
1 | #!/bin/sh | ||
2 | # __________ __ ___. | ||
3 | # Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | # \/ \/ \/ \/ \/ | ||
8 | # $Id$ | ||
9 | # | ||
10 | # Copyright 2006 Jonas Häggqvist, some parts Copyright 2004 Daniel Gudlat | ||
11 | # | ||
12 | # All files in this archive are subject to the GNU General Public License. | ||
13 | # See the file COPYING in the source tree root for full license agreement. | ||
14 | # | ||
15 | # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | # KIND, either express or implied. | ||
17 | |||
18 | # Include voicecommon.sh from the same dir as this script | ||
19 | # Any settings from voicecommon can be overridden if added below the following | ||
20 | # line. | ||
21 | source `dirname $0`'/voicecommon.sh' | ||
22 | |||
23 | #################### | ||
24 | # General settings # | ||
25 | #################### | ||
26 | |||
27 | # These settings can be overridden by passing a file with definitions as | ||
28 | # the fourth parameter to this script | ||
29 | |||
30 | # which TTS engine to use. Available: festival, flite, espeak | ||
31 | TTS_ENGINE=festival | ||
32 | # which encoder to use, available: lame, speex, vorbis (only lame will produce | ||
33 | # functional voice clips at this point) | ||
34 | ENCODER=lame | ||
35 | # Where to save temporary files | ||
36 | TEMPDIR=/tmp | ||
37 | |||
38 | ################### | ||
39 | # End of settings # | ||
40 | ################### | ||
41 | |||
42 | createvoicefile() { | ||
43 | $VOICEFONT "$LANG_FILE" "$TEMPDIR/" "./$RLANG.voice" | ||
44 | } | ||
45 | |||
46 | deletefiles() { | ||
47 | # XXX: might be unsafe depending on the value of TEMPDIR | ||
48 | rm -f "${TEMPDIR}"/LANG_* | ||
49 | rm -f "${TEMPDIR}"/VOICE_* | ||
50 | } | ||
51 | |||
52 | generateclips() { | ||
53 | ROCKBOX_DIR="$1" | ||
54 | RLANG="$2" | ||
55 | TARGET="$3" | ||
56 | GENLANG="$ROCKBOX_DIR"/tools/genlang | ||
57 | ENGLISH="$ROCKBOX_DIR"/apps/lang/english.lang | ||
58 | LANG_FILE="$ROCKBOX_DIR"/apps/lang/$RLANG.lang | ||
59 | |||
60 | $GENLANG -e=$ENGLISH -o -t=$TARGET $LANG_FILE |( | ||
61 | i=0 | ||
62 | while read line; do | ||
63 | case `expr $i % 3` in | ||
64 | 0) | ||
65 | # String ID no. | ||
66 | NUMBER=`echo $line |cut -b 2-` | ||
67 | ;; | ||
68 | 1) | ||
69 | # String ID | ||
70 | ID=`echo $line |cut -b 5-` | ||
71 | ;; | ||
72 | 2) | ||
73 | # String | ||
74 | STRING=`echo $line |cut -b 8-` | ||
75 | |||
76 | # Now generate the file | ||
77 | voice "$STRING" "$TEMPDIR/$ID".wav | ||
78 | encode "$TEMPDIR/$ID".wav "$TEMPDIR/$ID".mp3 | ||
79 | ;; | ||
80 | esac | ||
81 | i=`expr $i + 1` | ||
82 | done | ||
83 | ) | ||
84 | } | ||
85 | |||
86 | if [ -z "$3" ]; then | ||
87 | echo "Usage: $0 rockboxdirectory language target [settingsfile]"; | ||
88 | exit 32 | ||
89 | else | ||
90 | if [ ! -d "$1" ] || [ ! -f "$1/tools/genlang" ]; then | ||
91 | echo "Error: $1 is not a Rockbox directory" | ||
92 | exit 33 | ||
93 | fi | ||
94 | if [ ! -f "$1/apps/lang/$2.lang" ]; then | ||
95 | echo "Error: $2 is not a valid language" | ||
96 | exit 34 | ||
97 | fi | ||
98 | if [ ! -z "$4" ]; then | ||
99 | if [ -f "$4" ]; then | ||
100 | # Read settings from file | ||
101 | source "$4" | ||
102 | else | ||
103 | echo "Error: $4 does not exist" | ||
104 | exit 36 | ||
105 | fi | ||
106 | fi | ||
107 | # XXX: check for valid $TARGET? | ||
108 | fi | ||
109 | |||
110 | VOICEFONT=`dirname $0`/voicefont | ||
111 | if [ ! -x $VOICEFONT ]; then | ||
112 | echo "Error: $VOICEFONT does not exist or is not executable" | ||
113 | exit 35 | ||
114 | fi | ||
115 | |||
116 | init_tts | ||
117 | init_encoder | ||
118 | generateclips "$1" "$2" "$3" | ||
119 | stop_tts | ||
120 | createvoicefile | ||
121 | #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 @@ | |||
1 | #!/bin/sh | ||
2 | # __________ __ ___. | ||
3 | # Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | # \/ \/ \/ \/ \/ | ||
8 | # $Id$ | ||
9 | # | ||
10 | # Copyright (c) 2006 Jonas Häggqvist | ||
11 | # | ||
12 | # All files in this archive are subject to the GNU General Public License. | ||
13 | # See the file COPYING in the source tree root for full license agreement. | ||
14 | # | ||
15 | # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | # KIND, either express or implied. | ||
17 | # | ||
18 | # A selection of functions common to creating voicefiles for Rockbox. | ||
19 | # | ||
20 | # You may wish to change some of the settings below. | ||
21 | |||
22 | ##################### | ||
23 | # Program locations # | ||
24 | ##################### | ||
25 | |||
26 | # Leave any you're not using untouched, enter full path if the program is | ||
27 | # not found | ||
28 | |||
29 | # the festival main executable | ||
30 | FESTIVAL_BIN=festival | ||
31 | # the festival_client binary | ||
32 | FESTIVAL_CLIENT=festival_client | ||
33 | |||
34 | # The flite executable | ||
35 | FLITE_BIN=flite | ||
36 | |||
37 | # The eSpeak executable | ||
38 | ESPEAK_BIN=speak | ||
39 | |||
40 | # The lame executable | ||
41 | LAME_BIN=lame | ||
42 | |||
43 | # The speexenc executable | ||
44 | SPEEX_BIN=speexenc | ||
45 | |||
46 | # The oggenc executable | ||
47 | VORBIS_BIN=oggenc | ||
48 | |||
49 | # The wavtrim executable | ||
50 | WAVTRIM=`dirname $0`/wavtrim | ||
51 | |||
52 | ##################### | ||
53 | # Festival settings # | ||
54 | ##################### | ||
55 | |||
56 | # If you're not using festival, leave untouched | ||
57 | |||
58 | # whether to start the Festival server locally (Y/N) | ||
59 | FESTIVAL_START=Y | ||
60 | # the host of the Festival server | ||
61 | # this is set to localhost automatically when FESTIVAL_START is Y | ||
62 | FESTIVAL_HOST=localhost | ||
63 | # the port of the Festival server | ||
64 | FESTIVAL_PORT=1314 | ||
65 | # where to log the Festival client output | ||
66 | FESTIVAL_LOG=/dev/null | ||
67 | # other options to the festival client | ||
68 | FESTIVAL_OPTS="" | ||
69 | |||
70 | ################## | ||
71 | # Flite settings # | ||
72 | ################## | ||
73 | |||
74 | # If you're not using flite, leave untouched | ||
75 | FLITE_OPTS="" | ||
76 | |||
77 | ################### | ||
78 | # eSpeak settings # | ||
79 | ################### | ||
80 | |||
81 | # If you're not using eSpeak, leave untouched | ||
82 | ESPEAK_OPTS="" | ||
83 | |||
84 | #################### | ||
85 | # Wavtrim settings # | ||
86 | #################### | ||
87 | |||
88 | # The maximum sample value that will be treated as silence by the wavtrim tool. | ||
89 | # The value is expressed as an absolute 16 bit integer sample value (0 dB equals | ||
90 | # 32767). | ||
91 | # | ||
92 | # 500 is a good guess - at least for Festival | ||
93 | |||
94 | NOISEFLOOR='500' | ||
95 | |||
96 | ##################### | ||
97 | # Encoding settings # | ||
98 | ##################### | ||
99 | # where to log the encoder output | ||
100 | ENC_LOG=/dev/null | ||
101 | |||
102 | # Suggested: --vbr-new -t --nores -S | ||
103 | # VBR, independent frames, silent mode | ||
104 | LAME_OPTS="--vbr-new -t --nores -S" | ||
105 | |||
106 | # Suggested: | ||
107 | # XXX: suggest a default | ||
108 | SPEEX_OPTS="" | ||
109 | |||
110 | # Suggested: -q0 --downmix | ||
111 | # Low quality, mono | ||
112 | VORBIS_OPTS="-q0 --downmix" | ||
113 | |||
114 | ################### | ||
115 | # End of settings # | ||
116 | ################### | ||
117 | |||
118 | # Check if executables exist and perform any necessary initialisation | ||
119 | init_tts() { | ||
120 | case $TTS_ENGINE in | ||
121 | festival) | ||
122 | # Check for festival_client | ||
123 | if [ ! `which $FESTIVAL_CLIENT` ]; then | ||
124 | echo "Error: $FESTIVAL_CLIENT not found" | ||
125 | exit 4 | ||
126 | fi | ||
127 | |||
128 | # Check for, and start festival server if specified | ||
129 | if [ X$FESTIVAL_START = XY ]; then | ||
130 | if [ ! `which $FESTIVAL_BIN` ]; then | ||
131 | echo "Error: $FESTIVAL_BIN not found" | ||
132 | exit 3 | ||
133 | fi | ||
134 | FESTIVAL_HOST='localhost' | ||
135 | $FESTIVAL_BIN --server 2>&1 > /dev/null & | ||
136 | FESTIVAL_SERVER_PID=$! | ||
137 | sleep 3 | ||
138 | if [ `ps | grep -c "^\ *$FESTIVAL_SERVER_PID"` -ne 1 ]; then | ||
139 | echo "Error: Festival not started" | ||
140 | exit 9 | ||
141 | fi | ||
142 | fi | ||
143 | # Test connection to festival server | ||
144 | output=`echo -E "Rockbox" | $FESTIVAL_CLIENT --server \ | ||
145 | $FESTIVAL_HOST --otype riff --ttw --output \ | ||
146 | /dev/null 2>&1` | ||
147 | if [ $? -ne 0 ]; then | ||
148 | echo "Error: Couldn't connect to festival server at" \ | ||
149 | "$FESTIVAL_HOST ($output)" | ||
150 | exit 8 | ||
151 | fi | ||
152 | ;; | ||
153 | flite) | ||
154 | # Check for flite | ||
155 | if [ ! `which $FLITE_BIN` ]; then | ||
156 | echo "Error: $FLITE_BIN not found" | ||
157 | exit 5 | ||
158 | fi | ||
159 | ;; | ||
160 | espeak) | ||
161 | # Check for flite | ||
162 | if [ ! `which $ESPEAK_BIN` ]; then | ||
163 | echo "Error: $ESPEAK_BIN not found" | ||
164 | exit 5 | ||
165 | fi | ||
166 | ;; | ||
167 | *) | ||
168 | echo "Error: no valid TTS engine selected: $TTS_ENGINE" | ||
169 | exit 2 | ||
170 | ;; | ||
171 | esac | ||
172 | if [ ! -x $WAVTRIM ]; then | ||
173 | echo "Error: $WAVTRIM is not available" | ||
174 | exit 11 | ||
175 | fi | ||
176 | } | ||
177 | |||
178 | # Perform any necessary shutdown for TTS engine | ||
179 | stop_tts() { | ||
180 | case $TTS_ENGINE in | ||
181 | festival) | ||
182 | if [ X$FESTIVAL_START = XY ]; then | ||
183 | # XXX: This is probably possible to do using festival_client | ||
184 | kill $FESTIVAL_SERVER_PID > /dev/null 2>&1 | ||
185 | fi | ||
186 | ;; | ||
187 | esac | ||
188 | } | ||
189 | |||
190 | # Check if executables exist and perform any necessary initialisation | ||
191 | init_encoder() { | ||
192 | case $ENCODER in | ||
193 | lame) | ||
194 | # Check for lame binary | ||
195 | if [ ! `which $LAME_BIN` ]; then | ||
196 | echo "Error: $LAME_BIN not found" | ||
197 | exit 6 | ||
198 | fi | ||
199 | ;; | ||
200 | speex) | ||
201 | # Check for speexenc binary | ||
202 | if [ ! `which $SPEEX_BIN` ]; then | ||
203 | echo "Error: $SPEEX_BIN not found" | ||
204 | exit 7 | ||
205 | fi | ||
206 | ;; | ||
207 | vorbis) | ||
208 | # Check for vorbis encoder binary | ||
209 | if [ ! `which $VORBIS_BIN` ]; then | ||
210 | echo "Error: $VORBIS_BIN not found" | ||
211 | exit 10 | ||
212 | fi | ||
213 | ;; | ||
214 | *) | ||
215 | echo "Error: no valid encoder selected: $ENCODER" | ||
216 | exit 1 | ||
217 | ;; | ||
218 | esac | ||
219 | |||
220 | } | ||
221 | |||
222 | # Encode file $1 with ENCODER and save the result in $2, delete $1 if specified | ||
223 | encode() { | ||
224 | INPUT=$1 | ||
225 | OUTPUT=$2 | ||
226 | |||
227 | if [ ! -f "$INPUT" ]; then | ||
228 | echo "Warning: missing input file: \"$INPUT\"" | ||
229 | else | ||
230 | echo "Action: Encode $OUTPUT with $ENCODER" | ||
231 | case $ENCODER in | ||
232 | lame) | ||
233 | $LAME_BIN $LAME_OPTS "$WAV_FILE" "$OUTPUT" >>$ENC_LOG 2>&1 | ||
234 | ;; | ||
235 | speex) | ||
236 | $SPEEX_BIN $SPEEX_OPTS "$WAV_FILE" "$OUTPUT" >>$ENC_LOG 2>&1 | ||
237 | ;; | ||
238 | vorbis) | ||
239 | $VORBIS_BIN $VORBIS_OPTS "$WAV_FILE" -o "$OUTPUT" >>$ENC_LOG 2>&1 | ||
240 | esac | ||
241 | if [ ! -f "$OUTPUT" ]; then | ||
242 | echo "Warning: missing output file \"$OUTPUT\"" | ||
243 | fi | ||
244 | fi | ||
245 | } | ||
246 | |||
247 | # Generate file $2 containing $1 spoken by TTS_ENGINE, trim silence | ||
248 | voice() { | ||
249 | TO_SPEAK=$1 | ||
250 | WAV_FILE=$2 | ||
251 | if [ ! -f "$WAV_FILE" ] || [ X$OVERWRITE_WAV = XY ]; then | ||
252 | if [ "${TO_SPEAK}" == "" ]; then | ||
253 | touch "$WAV_FILE" | ||
254 | else | ||
255 | case $TTS_ENGINE in | ||
256 | festival) | ||
257 | echo "Action: Generate $WAV_FILE with festival" | ||
258 | echo -E "$TO_SPEAK" | $FESTIVAL_CLIENT $FESTIVAL_OPTS \ | ||
259 | --server $FESTIVAL_HOST \ | ||
260 | --otype riff --ttw --output "$WAV_FILE" 2>"$WAV_FILE" | ||
261 | ;; | ||
262 | espeak) | ||
263 | echo "Action: Generate $WAV_FILE with eSpeak" | ||
264 | echo $ESPEAK_BIN $ESPEAK_OPTS -w "$WAV_FILE" | ||
265 | echo -E "$TO_SPEAK" | $ESPEAK_BIN $ESPEAK_OPTS -w "$WAV_FILE" | ||
266 | ;; | ||
267 | flite) | ||
268 | echo "Action: Generate $WAV_FILE with flite" | ||
269 | echo -E "$TO_SPEAK" | $FLITE_BIN $FLITE_OPTS -o "$WAV_FILE" | ||
270 | ;; | ||
271 | esac | ||
272 | fi | ||
273 | fi | ||
274 | trim "$WAV_FILE" | ||
275 | } | ||
276 | |||
277 | # Trim wavefile $1 | ||
278 | trim() { | ||
279 | WAVEFILE="$1" | ||
280 | echo "Action: Trim $WAV_FILE" | ||
281 | $WAVTRIM "$WAVEFILE" $NOISEFLOOR | ||
282 | } | ||
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 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2004 by Jörg Hohensohn | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | * A tool to generate the Rockbox "voicefont", a collection of all the UI | ||
19 | * strings. | ||
20 | * | ||
21 | * Details at http://www.rockbox.org/twiki/bin/view/Main/VoiceBuilding | ||
22 | * | ||
23 | ****************************************************************************/ | ||
24 | |||
25 | #include <stdio.h> | ||
26 | #include <string.h> | ||
27 | |||
28 | /* endian conversion macros */ | ||
29 | #define SWAP2(x) ((((unsigned)(x)>>8) & 0x00ff) | (((unsigned)(x)<<8) & 0xff00)) | ||
30 | #define SWAP4(x) ((((unsigned)(x)>>24) & 0x000000ff) |\ | ||
31 | (((unsigned)(x)>>8) & 0x0000ff00) |\ | ||
32 | (((unsigned)(x)<<8) & 0x00ff0000) |\ | ||
33 | (((unsigned)(x)<<24) & 0xff000000)) | ||
34 | |||
35 | |||
36 | /* bitswap audio bytes, LSB becomes MSB and vice versa */ | ||
37 | int BitswapAudio (unsigned char* pDest, unsigned char* pSrc, size_t len) | ||
38 | { | ||
39 | static const unsigned char Lookup[256] = | ||
40 | { | ||
41 | 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, | ||
42 | 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, | ||
43 | 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, | ||
44 | 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, | ||
45 | 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, | ||
46 | 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, | ||
47 | 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, | ||
48 | 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, | ||
49 | 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, | ||
50 | 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, | ||
51 | 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, | ||
52 | 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, | ||
53 | 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, | ||
54 | 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, | ||
55 | 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, | ||
56 | 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF, | ||
57 | }; | ||
58 | |||
59 | while (len--) | ||
60 | *pDest++ = Lookup[*pSrc++]; | ||
61 | |||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | |||
66 | int main (int argc, char** argv) | ||
67 | { | ||
68 | FILE* pFile; | ||
69 | |||
70 | int i,j; | ||
71 | |||
72 | /* two tables, one for normal strings, one for voice-only (>0x8000) */ | ||
73 | static char names[1000][80]; /* worst-case space */ | ||
74 | char name[80]; /* one string ID */ | ||
75 | static int pos[1000]; /* position of sample */ | ||
76 | static int size[1000]; /* length of clip */ | ||
77 | int voiceonly[1000]; /* flag if this is voice only */ | ||
78 | int count = 0; | ||
79 | int count_voiceonly = 0; | ||
80 | unsigned int value; /* value to be written to file */ | ||
81 | static unsigned char buffer[65535]; /* clip buffer, allow only 64K */ | ||
82 | int fields; | ||
83 | char line[255]; /* one line from the .lang file */ | ||
84 | char mp3filename1[1024]; | ||
85 | char mp3filename2[1024]; | ||
86 | char* mp3filename; | ||
87 | FILE* pMp3File; | ||
88 | |||
89 | |||
90 | if (argc < 2) | ||
91 | { | ||
92 | printf("Makes a Rockbox voicefont from a collection of mp3 clips.\n"); | ||
93 | printf("Usage: voicefont <language file> <mp3 path> <output file>\n"); | ||
94 | printf("\n"); | ||
95 | printf("Example: \n"); | ||
96 | printf("voicefont english.lang voice\\ voicefont.bin\n"); | ||
97 | return -1; | ||
98 | } | ||
99 | |||
100 | pFile = fopen(argv[1], "r"); | ||
101 | if (pFile == NULL) | ||
102 | { | ||
103 | printf("Error opening language file %s\n", argv[1]); | ||
104 | return -2; | ||
105 | } | ||
106 | |||
107 | memset(voiceonly, 0, sizeof(voiceonly)); | ||
108 | while (!feof(pFile)) | ||
109 | { | ||
110 | fgets(line, sizeof(line), pFile); | ||
111 | if (line[0] == '#') /* comment */ | ||
112 | continue; | ||
113 | |||
114 | fields = sscanf(line, " id: %s", name); | ||
115 | if (fields == 1) | ||
116 | { | ||
117 | count++; /* next entry started */ | ||
118 | strcpy(names[count-1], name); | ||
119 | if (strncmp("VOICE_", name, 6) == 0) /* voice-only id? */ | ||
120 | voiceonly[count-1] = 1; | ||
121 | continue; | ||
122 | } | ||
123 | } | ||
124 | fclose(pFile); | ||
125 | |||
126 | pFile = fopen(argv[3], "wb"); | ||
127 | if (pFile == NULL) | ||
128 | { | ||
129 | printf("Error opening output file %s\n", argv[3]); | ||
130 | return -2; | ||
131 | } | ||
132 | fseek(pFile, 16 + count*8, SEEK_SET); /* space for header */ | ||
133 | |||
134 | for (i=0; i<count; i++) | ||
135 | { | ||
136 | if (voiceonly[i] == 1) | ||
137 | count_voiceonly++; | ||
138 | |||
139 | pos[i] = ftell(pFile); | ||
140 | sprintf(mp3filename1, "%s%s.mp3", argv[2], names[i]); | ||
141 | sprintf(mp3filename2, "%s%s.wav.mp3", argv[2], names[i]); | ||
142 | mp3filename = mp3filename1; | ||
143 | pMp3File = fopen(mp3filename, "rb"); | ||
144 | if (pMp3File == NULL) | ||
145 | { /* alternatively, try the lame default filename */ | ||
146 | mp3filename = mp3filename2; | ||
147 | pMp3File = fopen(mp3filename, "rb"); | ||
148 | if (pMp3File == NULL) | ||
149 | { | ||
150 | printf("mp3 file %s not found!\n", mp3filename1); | ||
151 | size[i] = 0; | ||
152 | continue; | ||
153 | } | ||
154 | } | ||
155 | printf("processing %s\n", mp3filename); | ||
156 | |||
157 | size[i] = fread(buffer, 1, sizeof(buffer), pMp3File); | ||
158 | fclose(pMp3File); | ||
159 | BitswapAudio(buffer, buffer, size[i]); | ||
160 | fwrite(buffer, 1, size[i], pFile); | ||
161 | |||
162 | printf("%d %s %d\n", i, names[i], size[i]); /* debug */ | ||
163 | } /* for i */ | ||
164 | |||
165 | |||
166 | fseek(pFile, 0, SEEK_SET); | ||
167 | |||
168 | /* Create the file format: */ | ||
169 | |||
170 | /* 1st 32 bit value in the file is the version number */ | ||
171 | value = SWAP4(200); /* 2.00 */ | ||
172 | fwrite(&value, sizeof(value), 1, pFile); | ||
173 | |||
174 | /* 2nd 32 bit value in the file is the header size (= 1st table position) */ | ||
175 | value = SWAP4(16); /* 16 bytes: for version, header size, number1, number2 */ | ||
176 | fwrite(&value, sizeof(value), 1, pFile); | ||
177 | |||
178 | /* 3rd 32 bit value in the file is the number of clips in 1st table */ | ||
179 | value = SWAP4(count-count_voiceonly); | ||
180 | fwrite(&value, sizeof(value), 1, pFile); | ||
181 | |||
182 | /* 4th bit value in the file is the number of clips in 2nd table */ | ||
183 | value = SWAP4(count_voiceonly); | ||
184 | fwrite(&value, sizeof(value), 1, pFile); | ||
185 | |||
186 | /* then followed by offset/size pairs for each clip */ | ||
187 | for (j=0; j<2; j++) /* now 2 tables */ | ||
188 | { | ||
189 | for (i=0; i<count; i++) | ||
190 | { | ||
191 | if (j == 0) /* first run, skip the voice only ones */ | ||
192 | { | ||
193 | if (voiceonly[i] == 1) | ||
194 | continue; | ||
195 | } | ||
196 | else /* second run, skip the non voice only ones */ | ||
197 | { | ||
198 | if (!voiceonly[i] == 1) | ||
199 | continue; | ||
200 | } | ||
201 | |||
202 | value = SWAP4(pos[i]); /* position */ | ||
203 | fwrite(&value, sizeof(value), 1, pFile); | ||
204 | value = SWAP4(size[i]); /* size */ | ||
205 | fwrite(&value, sizeof(value), 1, pFile); | ||
206 | } /* for i */ | ||
207 | } /* for j */ | ||
208 | |||
209 | |||
210 | /* | ||
211 | * after this the actual bitswapped mp3 data follows, | ||
212 | * which we already have written, see above. | ||
213 | */ | ||
214 | |||
215 | fclose(pFile); | ||
216 | |||
217 | return 0; | ||
218 | } | ||
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 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2004 by Jörg Hohensohn | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | * Details at http://www.rockbox.org/twiki/bin/view/Main/VoiceBuilding | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | |||
23 | #include <stdio.h> /* for file I/O */ | ||
24 | #include <stdlib.h> /* for malloc */ | ||
25 | |||
26 | /* place a 32 bit value into memory, little endian */ | ||
27 | void Write32(unsigned char* pByte, unsigned long value) | ||
28 | { | ||
29 | pByte[0] = (unsigned char)value; | ||
30 | pByte[1] = (unsigned char)(value >> 8); | ||
31 | pByte[2] = (unsigned char)(value >> 16); | ||
32 | pByte[3] = (unsigned char)(value >> 24) ; | ||
33 | } | ||
34 | |||
35 | |||
36 | /* read a 32 bit value from memory, little endian */ | ||
37 | unsigned long Read32(unsigned char* pByte) | ||
38 | { | ||
39 | unsigned long value = 0; | ||
40 | |||
41 | value |= (unsigned long)pByte[0]; | ||
42 | value |= (unsigned long)pByte[1] << 8; | ||
43 | value |= (unsigned long)pByte[2] << 16; | ||
44 | value |= (unsigned long)pByte[3] << 24; | ||
45 | |||
46 | return value; | ||
47 | } | ||
48 | |||
49 | |||
50 | /* place a 16 bit value into memory, little endian */ | ||
51 | void Write16(unsigned char* pByte, unsigned short value) | ||
52 | { | ||
53 | pByte[0] = (unsigned char)value; | ||
54 | pByte[1] = (unsigned char)(value >> 8); | ||
55 | } | ||
56 | |||
57 | |||
58 | /* read a 16 bit value from memory, little endian */ | ||
59 | unsigned long Read16(unsigned char* pByte) | ||
60 | { | ||
61 | unsigned short value = 0; | ||
62 | |||
63 | value |= (unsigned short)pByte[0]; | ||
64 | value |= (unsigned short)pByte[1] << 8; | ||
65 | |||
66 | return value; | ||
67 | } | ||
68 | |||
69 | |||
70 | int main (int argc, char** argv) | ||
71 | { | ||
72 | FILE* pFile; | ||
73 | long lFileSize, lGot; | ||
74 | unsigned char* pBuf; | ||
75 | int bps; /* byte per sample */ | ||
76 | int sps; /* samples per second */ | ||
77 | int datapos; /* where the payload starts */ | ||
78 | int skip_head, skip_tail, pad_head, pad_tail; | ||
79 | int i; | ||
80 | int max_silence = 0; | ||
81 | signed char sample8; | ||
82 | short sample16; | ||
83 | |||
84 | if (argc < 2) | ||
85 | { | ||
86 | printf("wavtrim removes silence at the begin and end of a WAV file.\n"); | ||
87 | printf("usage: wavtrim <filename.wav> [<max_silence>]\n"); | ||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | if (argc == 3) | ||
92 | { | ||
93 | max_silence = atoi(argv[2]); | ||
94 | } | ||
95 | |||
96 | pFile = fopen(argv[1], "rb"); | ||
97 | if (pFile == NULL) | ||
98 | { | ||
99 | printf("Error opening file %s for reading\n", argv[1]); | ||
100 | return -1; | ||
101 | } | ||
102 | |||
103 | fseek(pFile, 0, SEEK_END); | ||
104 | lFileSize = ftell(pFile); | ||
105 | fseek(pFile, 0, SEEK_SET); | ||
106 | |||
107 | pBuf = malloc(lFileSize); | ||
108 | if (pBuf == NULL) | ||
109 | { | ||
110 | printf("Out of memory to allocate %ld bytes for file.\n", lFileSize); | ||
111 | fclose(pFile); | ||
112 | return -1; | ||
113 | } | ||
114 | |||
115 | lGot = fread(pBuf, 1, lFileSize, pFile); | ||
116 | fclose(pFile); | ||
117 | if (lGot != lFileSize) | ||
118 | { | ||
119 | printf("File read error, got only %ld bytes out of %ld.\n", lGot, lFileSize); | ||
120 | free(pBuf); | ||
121 | return -1; | ||
122 | } | ||
123 | |||
124 | bps = Read16(pBuf + 32); | ||
125 | datapos = 28 + Read16(pBuf + 16); | ||
126 | |||
127 | if (Read32(pBuf) != 0x46464952 /* "RIFF" */ | ||
128 | || Read32(pBuf+8) != 0x45564157 /* "WAVE" */ | ||
129 | || Read32(pBuf+12) != 0x20746d66 /* "fmt " */ | ||
130 | || Read32(pBuf+datapos-8) != 0x61746164) /* "data" */ | ||
131 | { | ||
132 | printf("No valid input WAV file?\n", lGot, lFileSize); | ||
133 | free(pBuf); | ||
134 | return -1; | ||
135 | } | ||
136 | |||
137 | sps = Read32(pBuf + 24); | ||
138 | pad_head = sps * 10 / 1000; /* 10 ms */ | ||
139 | pad_tail = sps * 10 / 1000; /* 10 ms */ | ||
140 | |||
141 | if (bps == 1) /* 8 bit samples */ | ||
142 | { | ||
143 | |||
144 | max_silence >>= 8; | ||
145 | |||
146 | /* clip the start */ | ||
147 | for (i=datapos; i<lFileSize; i++) | ||
148 | { | ||
149 | sample8 = pBuf[i] - 0x80; | ||
150 | if (abs(sample8) > max_silence) | ||
151 | break; | ||
152 | } | ||
153 | skip_head = i - datapos; | ||
154 | skip_head = (skip_head > pad_head) ? skip_head - pad_head : 0; | ||
155 | |||
156 | /* clip the end */ | ||
157 | for (i=lFileSize-1; i>datapos+skip_head; i--) | ||
158 | { | ||
159 | sample8 = pBuf[i] - 0x80; | ||
160 | if (abs(sample8) > max_silence) | ||
161 | break; | ||
162 | } | ||
163 | skip_tail = lFileSize - 1 - i; | ||
164 | skip_tail = (skip_tail > pad_tail) ? skip_tail - pad_tail : 0; | ||
165 | } | ||
166 | else if (bps == 2) /* 16 bit samples */ | ||
167 | { | ||
168 | |||
169 | /* clip the start */ | ||
170 | for (i=datapos; i<lFileSize; i+=2) | ||
171 | { | ||
172 | sample16 = *(short *)(pBuf + i); | ||
173 | if (abs(sample16) > max_silence) | ||
174 | break; | ||
175 | } | ||
176 | skip_head = i - datapos; | ||
177 | skip_head = (skip_head > 2 * pad_head) ? | ||
178 | skip_head - 2 * pad_head : 0; | ||
179 | |||
180 | /* clip the end */ | ||
181 | for (i=lFileSize-2; i>datapos+skip_head; i-=2) | ||
182 | { | ||
183 | sample16 = *(short *)(pBuf + i); | ||
184 | if (abs(sample16) > max_silence) | ||
185 | break; | ||
186 | } | ||
187 | skip_tail = lFileSize - 2 - i; | ||
188 | skip_tail = (skip_tail > 2 * pad_tail) ? | ||
189 | skip_tail - 2 * pad_tail : 0; | ||
190 | } | ||
191 | |||
192 | /* update the size in the headers */ | ||
193 | Write32(pBuf+4, Read32(pBuf+4) - skip_head - skip_tail); | ||
194 | Write32(pBuf+datapos-4, Read32(pBuf+datapos-4) - skip_head - skip_tail); | ||
195 | |||
196 | pFile = fopen(argv[1], "wb"); | ||
197 | if (pFile == NULL) | ||
198 | { | ||
199 | printf("Error opening file %s for writing\n", argv[1]); | ||
200 | return -1; | ||
201 | } | ||
202 | |||
203 | /* write the new file */ | ||
204 | fwrite(pBuf, 1, datapos, pFile); /* write header */ | ||
205 | fwrite(pBuf + datapos + skip_head, 1, lFileSize - datapos - skip_head - skip_tail, pFile); | ||
206 | fclose(pFile); | ||
207 | |||
208 | free(pBuf); | ||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | /* | ||
213 | RIFF Chunk (12 bytes in length total) | ||
214 | 0 - 3 "RIFF" (ASCII Characters) | ||
215 | 4 - 7 Total Length Of Package To Follow (Binary, little endian) | ||
216 | 8 - 11 "WAVE" (ASCII Characters) | ||
217 | |||
218 | |||
219 | FORMAT Chunk (24 or 26 bytes in length total) Byte Number | ||
220 | 12 - 15 "fmt_" (ASCII Characters) | ||
221 | 16 - 19 Length Of FORMAT Chunk (Binary, 0x10 or 0x12 seen) | ||
222 | 20 - 21 Always 0x01 | ||
223 | 22 - 23 Channel Numbers (Always 0x01=Mono, 0x02=Stereo) | ||
224 | 24 - 27 Sample Rate (Binary, in Hz) | ||
225 | 28 - 31 Bytes Per Second | ||
226 | 32 - 33 Bytes Per Sample: 1=8 bit Mono, 2=8 bit Stereo or 16 bit Mono, 4=16 bit Stereo | ||
227 | 34 - 35 Bits Per Sample | ||
228 | |||
229 | |||
230 | DATA Chunk Byte Number | ||
231 | 36 - 39 "data" (ASCII Characters) | ||
232 | 40 - 43 Length Of Data To Follow | ||
233 | 44 - end | ||
234 | Data (Samples) | ||
235 | */ | ||