Skip to content

Commit

Permalink
Implement custom silence for sentences, questions and exclamations
Browse files Browse the repository at this point in the history
Re: #18.

Custom silence options have been added to the settings for these th-
ree textual elements. By default, the application is set to defer to
the TTS engine as before.

I have also added two silence duration choices: 750 ms and 1000 ms.

This changset marks the end of the work planned for issue #18.
  • Loading branch information
drmfinlay committed Jul 1, 2022
1 parent db6aed3 commit d0fb6e9
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 13 deletions.
50 changes: 44 additions & 6 deletions app/src/main/java/com/danefinlay/ttsutil/TTSTasks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,
private val scaleSilenceToRate: Boolean
private val speechRate: Float
private val delimitersToSilenceMap: Map<Int, Long>
private val endOfTextDelimiters: Set<Int>

private val filterHashes: Boolean
private val filterWebLinks: Boolean
Expand Down Expand Up @@ -119,11 +120,34 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,
val prefs = PreferenceManager.getDefaultSharedPreferences(app)
scaleSilenceToRate = prefs.getBoolean("pref_scale_silence_to_rate", false)
speechRate = prefs.getFloat("pref_tts_speech_rate", 1.0f)
val silLF = prefs.getString("pref_silence_line_endings", "200")!!.toLong()
val sentenceSil = prefs.getString("pref_silence_sentences", "0")!!.toLong()
val questionSil = prefs.getString("pref_silence_questions", "0")!!.toLong()
val excSil = prefs.getString("pref_silence_exclamations", "0")!!.toLong()

// Set the delimiters to silence map, aliasing the Unicode "halfwidth" and
// "fullwidth" forms as necessary.
delimitersToSilenceMap = mapOf(
// Line Feed (\n).
0x0a to prefs.getString("pref_silence_line_endings", "100")
!!.toLong()
0x000a to silLF,

// Sentences:
// Full stops. Ellipses.
0x002e to sentenceSil, 0x2026 to sentenceSil,
0xff0e to sentenceSil,
0xff61 to sentenceSil,

// Questions. Exclamations.
0x003f to questionSil, 0x0021 to excSil,
0xff1f to questionSil, 0xff01 to excSil,
)

// The set of end-of-text delimiters includes all non-whitespace delimiters.
val endOfTextDelimiters = delimitersToSilenceMap.keys.toMutableSet()
endOfTextDelimiters.remove(0x000a)
this.endOfTextDelimiters = endOfTextDelimiters

// Set variables related to input filtering.
filterHashes = prefs.getBoolean("pref_filter_hash", false)
filterWebLinks = prefs.getBoolean("pref_filter_web_links", false)
filterMailToLinks = prefs.getBoolean("pref_filter_mailto_links", false)
Expand Down Expand Up @@ -229,9 +253,9 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,
val buffer = ArrayList<Char>()
var bytesRead = 0

// Read characters until we hit a delimiter with a positive silence duration
// or until an appropriate whitespace character is found near the maximum
// input length -- whichever comes first.
// Read characters until we hit an appropriate delimiter with a positive
// silence duration or until an appropriate whitespace character is found
// near the maximum input length -- whichever comes first.
var byte = streamReader.read()
var silenceDuration = 0L
while (byte >= 0) {
Expand All @@ -242,7 +266,21 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,
val char = byte.toChar()
silenceDuration = delimitersToSilenceMap[char.toInt()] ?: 0L
if (silenceDuration == 0L) buffer.add(char)
else if (silenceDuration > 0) break
else if (silenceDuration > 0) {
// There should be at least one non-delimiting, non-whitespace
// character before this one for it to count as a delimiter.
// This rule prevents silence from being inserted for, e.g.,
// successive question marks, which methinks would be a poor
// imitation of human speech.
if (byte in endOfTextDelimiters) {
val lastChar = buffer.lastOrNull()
if (lastChar != null && !lastChar.isWhitespace() &&
lastChar.toInt() !in delimitersToSilenceMap) break
}

// This is a whitespace delimiter: break.
else break
}

if (buffer.size >= lineFeedScanThreshold && byte == 0x0a) break
if (buffer.size >= whitespaceScanThreshold && char.isWhitespace()) break
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,24 @@
- limitations under the License.
-->
<resources>
<string-array name="silence_line_endings_entries">
<item>Let the engine decide</item>
<string-array name="silence_entries">
<item>Let the engine decide.</item>
<item>100 ms</item>
<item>200 ms</item>
<item>300 ms</item>
<item>400 ms</item>
<item>500 ms</item>
<item>750 ms</item>
<item>1000 ms</item>
</string-array>
<string-array name="silence_line_endings_values">
<string-array name="silence_values">
<item>0</item>
<item>100</item>
<item>200</item>
<item>300</item>
<item>400</item>
<item>500</item>
<item>750</item>
<item>1000</item>
</string-array>
</resources>
6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@
<string name="pref_silence_category_summary">Insert custom pauses between text.</string>
<string name="pref_silence_line_endings">Line endings</string>
<string name="pref_silence_line_endings_summary">Set the number of milliseconds to pause after each line.</string>
<string name="pref_silence_sentences">Sentences</string>
<string name="pref_silence_sentences_summary">Set the number of milliseconds to pause after a sentence, i.e. after a full stop or ellipsis.</string>
<string name="pref_silence_questions">Questions</string>
<string name="pref_silence_questions_summary">Set the number of milliseconds to pause after a question, e.g. \"How could this happen?\"</string>
<string name="pref_silence_exclamations">Exclamations</string>
<string name="pref_silence_exclamations_summary">Set the number of milliseconds to pause after an exclamation, e.g. \"No way!\"</string>

<string name="pref_input_category">Input Processing</string>
<string name="pref_input_category_summary">Change how input text is processed.</string>
Expand Down
35 changes: 31 additions & 4 deletions app/src/main/res/xml/prefs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,37 @@
android:key="pref_silence_line_endings"
android:title="@string/pref_silence_line_endings"
android:summary="@string/pref_silence_line_endings_summary"
android:dialogTitle="@string/pref_silence_line_endings_summary"
android:entries="@array/silence_line_endings_entries"
android:entryValues="@array/silence_line_endings_values"
android:defaultValue="100" />
android:dialogTitle="@string/pref_silence_line_endings"
android:entries="@array/silence_entries"
android:entryValues="@array/silence_values"
android:defaultValue="200" />

<ListPreference
android:key="pref_silence_sentences"
android:title="@string/pref_silence_sentences"
android:summary="@string/pref_silence_sentences_summary"
android:dialogTitle="@string/pref_silence_sentences"
android:entries="@array/silence_entries"
android:entryValues="@array/silence_values"
android:defaultValue="0" />

<ListPreference
android:key="pref_silence_questions"
android:title="@string/pref_silence_questions"
android:summary="@string/pref_silence_questions_summary"
android:dialogTitle="@string/pref_silence_questions"
android:entries="@array/silence_entries"
android:entryValues="@array/silence_values"
android:defaultValue="0" />

<ListPreference
android:key="pref_silence_exclamations"
android:title="@string/pref_silence_exclamations"
android:summary="@string/pref_silence_exclamations_summary"
android:dialogTitle="@string/pref_silence_exclamations"
android:entries="@array/silence_entries"
android:entryValues="@array/silence_values"
android:defaultValue="0" />

</PreferenceCategory>

Expand Down

0 comments on commit d0fb6e9

Please sign in to comment.